From 787a3131b161d4e805537f6aadfb1a65fe41f278 Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Mon, 12 Oct 2026 20:13:02 +0500 Subject: [PATCH 1/9] chore: enforce generated SDK quality and contract gates --- .github/workflows/quality.yml | 61 + .gitignore | 7 + .husky/pre-commit | 1 + .nvmrc | 1 + AGENTS.md | 708 +- composer.lock | 5097 ++++++++++++ eslint.config.mjs | 13 + knip.json | 12 + lib/Api/CallingApi.php | 2 +- lib/Api/CatalogApi.php | 2 +- lib/Api/ChannelApi.php | 759 +- lib/Api/FlowsApi.php | 152 +- lib/Api/GroupsApi.php | 176 +- lib/Api/MessagingApi.php | 350 +- lib/Api/ProfileApi.php | 40 +- lib/Api/TemplatesApi.php | 117 +- lib/Api/UsersApi.php | 2 +- lib/Api/WebhooksApi.php | 54 +- lib/ApiException.php | 2 +- lib/Configuration.php | 2 +- lib/FormDataProcessor.php | 2 +- lib/HeaderSelector.php | 2 +- lib/Model/AddTemplate200Response.php | 410 + lib/Model/AddTemplateRequest.php | 10 +- lib/Model/BlockUserRequest.php | 2 +- lib/Model/CallingSettings.php | 2 +- lib/Model/CallingSettingsCalling.php | 2 +- lib/Model/CallingSettingsCallingAudio.php | 2 +- lib/Model/CallingSettingsCallingCallHours.php | 2 +- lib/Model/CallingSettingsCallingSip.php | 2 +- .../CallingSettingsCallingSipServersInner.php | 2 +- lib/Model/CallingSettingsCallingVideo.php | 2 +- lib/Model/ConversationalAutomation.php | 2 +- .../ConversationalAutomationCommandsInner.php | 2 +- lib/Model/CreateCommerce200Response.php | 2 +- lib/Model/CreateCommerceRequest.php | 2 +- lib/Model/CreateCommerceRequestParams.php | 2 +- lib/Model/CreateFlowsRequest.php | 588 ++ lib/Model/CreateGroups200Response.php | 2 +- lib/Model/CreateGroupsGroupIdRequest.php | 478 ++ lib/Model/CreateGroupsRequest.php | 447 ++ lib/Model/CreateReadMessage200Response.php | 2 +- lib/Model/CreateReadMessage401Response.php | 2 +- lib/Model/CreateReadMessageRequest.php | 2 +- lib/Model/CreateSettingsRequest.php | 580 ++ lib/Model/CreateSettingsRequestWebhookUrl.php | 383 + lib/Model/CreateUploadMedia200Response.php | 410 + lib/Model/CreateUploadMediaRequest.php | 444 ++ lib/Model/DeleteFlowsFlowId200Response.php | 2 +- lib/Model/DeleteGroupsGroupId200Response.php | 2 +- lib/Model/DeleteMediaLegacyRequest.php | 2 +- lib/Model/ErrorResponse.php | 2 +- lib/Model/GetCommerce200ResponseInner.php | 2 +- lib/Model/GetMmLiteStatus200Response.php | 2 +- lib/Model/GetStatus200Response.php | 546 ++ lib/Model/GetWebhook200Response.php | 441 +- lib/Model/GetWebhook200ResponseWebhookUrl.php | 383 + ...tWhatsappBusinessEncryption200Response.php | 2 +- lib/Model/InitiateCallRequest.php | 2 +- lib/Model/InitiateCallRequestSession.php | 2 +- lib/Model/InitiateCallResponse.php | 2 +- lib/Model/InitiateCallResponseCallsInner.php | 2 +- lib/Model/InitiateCallResponseResponse.php | 2 +- lib/Model/ListBlockedUsers200Response.php | 2 +- lib/Model/ListFlows200Response.php | 2 +- lib/Model/ListMessages200Response.php | 2 +- lib/Model/ListSettings200Response.php | 1028 +++ .../ListSettings200ResponseWebhookUrl.php | 383 + lib/Model/ListTemplates200Response.php | 2 +- lib/Model/MessageSentResponse.php | 2 +- lib/Model/ModelInterface.php | 2 +- .../PatchFlowsFlowIdAssets200Response.php | 2 +- lib/Model/PatchFlowsFlowIdAssetsRequest.php | 413 + lib/Model/PatchFlowsFlowIdMetadataRequest.php | 514 ++ lib/Model/ProfileInfo.php | 2 +- lib/Model/RemoveTemplateRequest.php | 413 + lib/Model/RetrieveMedia200Response.php | 2 +- lib/Model/SendAddressMessageRequest.php | 2 +- lib/Model/SendButtonRequest.php | 140 +- lib/Model/SendButtonRequestSectionsInner.php | 2 +- .../SendButtonRequestSectionsInnerReply.php | 2 +- lib/Model/SendCarouselRequest1.php | 58 +- lib/Model/SendContactRequest.php | 106 +- lib/Model/SendContactRequestContact.php | 553 ++ lib/Model/SendContactRequestContactName.php | 383 + lib/Model/SendContactRequestContactsInner.php | 20 +- .../SendContactRequestContactsInnerName.php | 2 +- .../SendContactRequestContactsInnerOrg.php | 2 +- ...ContactRequestContactsInnerPhonesInner.php | 6 +- lib/Model/SendCtaUrlRequest.php | 2 +- lib/Model/SendFileRequest.php | 2 +- lib/Model/SendFlowRequest1.php | 2 +- lib/Model/SendFlowRequest1Header.php | 2 +- lib/Model/SendFlowRequestHeader.php | 2 +- lib/Model/SendListRequest.php | 72 +- lib/Model/SendListRequestSectionsInner.php | 2 +- .../SendListRequestSectionsInnerRowsInner.php | 2 +- lib/Model/SendLocationRequest1.php | 2 +- lib/Model/SendLocationRequestRequest.php | 72 +- lib/Model/SendMessageRequest.php | 13 +- lib/Model/SendOrderDetailsRequest.php | 2 +- lib/Model/SendOrderDetailsRequestLanguage.php | 2 +- lib/Model/SendOrderDetailsRequestOrder.php | 2 +- ...SendOrderDetailsRequestOrderItemsInner.php | 2 +- ...derDetailsRequestOrderItemsInnerAmount.php | 2 +- lib/Model/SendPaymentRequestRequest.php | 2 +- lib/Model/SendProductRequest.php | 617 ++ lib/Model/SendReactionRequest.php | 38 +- lib/Model/SendStickerRequest.php | 2 +- lib/Model/SendTemplateRequest.php | 82 +- lib/Model/SendTemplateRequestLanguage.php | 7 +- lib/Model/SetWebhook200Response.php | 444 ++ lib/Model/SetWebhook200ResponseWebhookUrl.php | 383 + lib/Model/SetWebhookRequest.php | 413 + lib/Model/SetWebhookRequestWebhookUrl.php | 383 + .../SetWhatsappBusinessEncryptionRequest.php | 2 +- lib/Model/SuccessResponse.php | 2 +- .../UpdateCallingSettings200Response.php | 2 +- ...dateCallingSettings200ResponseResponse.php | 2 +- lib/Model/UpdateMe200Response.php | 410 + lib/Model/UpdateMeRequest.php | 10 +- lib/Model/WebhookUrlStatus.php | 729 ++ lib/ObjectSerializer.php | 2 +- package-lock.json | 15 + package.json | 25 + quality.config.mjs | 14 + quality/contract-smoke.php | 40 + quality/generator/generate.mjs | 145 + quality/generator/inventory.json | 117 + quality/generator/openapi.yaml | 4621 +++++++++++ quality/generator/provenance.json | 111 + quality/generator/verify-negative.mjs | 58 + quality/postprocess.mjs | 22 + tools/quality/README.md | 85 + tools/quality/eslint.mjs | 137 + tools/quality/lib/baseline.mjs | 82 + tools/quality/lib/git.mjs | 86 + tools/quality/lib/guards.mjs | 356 + tools/quality/lib/historical.mjs | 139 + tools/quality/lib/knip-reporter.mjs | 42 + tools/quality/lib/reports.mjs | 265 + tools/quality/lib/template.mjs | 56 + tools/quality/node_modules | 1 + tools/quality/package-lock.json | 6852 +++++++++++++++++ tools/quality/package.json | 34 + tools/quality/prettier.config.mjs | 2 + tools/quality/run.mjs | 360 + tools/quality/seal.mjs | 19 + tools/quality/template-manifest.json | 25 + tools/quality/test/cli.test.mjs | 510 ++ tools/quality/test/ratchet.test.mjs | 238 + tools/quality/tsconfig.next.json | 16 + tools/quality/tsconfig.node.json | 13 + tools/quality/tsconfig.svelte.json | 14 + 154 files changed, 33856 insertions(+), 1142 deletions(-) create mode 100644 .github/workflows/quality.yml create mode 100644 .husky/pre-commit create mode 100644 .nvmrc create mode 100644 composer.lock create mode 100644 eslint.config.mjs create mode 100644 knip.json create mode 100644 lib/Model/AddTemplate200Response.php create mode 100644 lib/Model/CreateFlowsRequest.php create mode 100644 lib/Model/CreateGroupsGroupIdRequest.php create mode 100644 lib/Model/CreateGroupsRequest.php create mode 100644 lib/Model/CreateSettingsRequest.php create mode 100644 lib/Model/CreateSettingsRequestWebhookUrl.php create mode 100644 lib/Model/CreateUploadMedia200Response.php create mode 100644 lib/Model/CreateUploadMediaRequest.php create mode 100644 lib/Model/GetStatus200Response.php create mode 100644 lib/Model/GetWebhook200ResponseWebhookUrl.php create mode 100644 lib/Model/ListSettings200Response.php create mode 100644 lib/Model/ListSettings200ResponseWebhookUrl.php create mode 100644 lib/Model/PatchFlowsFlowIdAssetsRequest.php create mode 100644 lib/Model/PatchFlowsFlowIdMetadataRequest.php create mode 100644 lib/Model/RemoveTemplateRequest.php create mode 100644 lib/Model/SendContactRequestContact.php create mode 100644 lib/Model/SendContactRequestContactName.php create mode 100644 lib/Model/SendProductRequest.php create mode 100644 lib/Model/SetWebhook200Response.php create mode 100644 lib/Model/SetWebhook200ResponseWebhookUrl.php create mode 100644 lib/Model/SetWebhookRequest.php create mode 100644 lib/Model/SetWebhookRequestWebhookUrl.php create mode 100644 lib/Model/UpdateMe200Response.php create mode 100644 lib/Model/WebhookUrlStatus.php create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 quality.config.mjs create mode 100644 quality/contract-smoke.php create mode 100644 quality/generator/generate.mjs create mode 100644 quality/generator/inventory.json create mode 100644 quality/generator/openapi.yaml create mode 100644 quality/generator/provenance.json create mode 100644 quality/generator/verify-negative.mjs create mode 100644 quality/postprocess.mjs create mode 100644 tools/quality/README.md create mode 100644 tools/quality/eslint.mjs create mode 100644 tools/quality/lib/baseline.mjs create mode 100644 tools/quality/lib/git.mjs create mode 100644 tools/quality/lib/guards.mjs create mode 100644 tools/quality/lib/historical.mjs create mode 100644 tools/quality/lib/knip-reporter.mjs create mode 100644 tools/quality/lib/reports.mjs create mode 100644 tools/quality/lib/template.mjs create mode 120000 tools/quality/node_modules create mode 100644 tools/quality/package-lock.json create mode 100644 tools/quality/package.json create mode 100644 tools/quality/prettier.config.mjs create mode 100644 tools/quality/run.mjs create mode 100644 tools/quality/seal.mjs create mode 100644 tools/quality/template-manifest.json create mode 100644 tools/quality/test/cli.test.mjs create mode 100644 tools/quality/test/ratchet.test.mjs create mode 100644 tools/quality/tsconfig.next.json create mode 100644 tools/quality/tsconfig.node.json create mode 100644 tools/quality/tsconfig.svelte.json diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..cb86db2 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,61 @@ +name: Quality +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +concurrency: + group: quality-${{ github.ref }} + cancel-in-progress: true +jobs: + contract: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: "24.19.0" + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "24" + - run: npm ci + - run: npm --prefix tools/quality ci + - run: node --test tools/quality/test/*.test.mjs + - run: node tools/quality/run.mjs guards + - run: npm run format:check + - run: npm run lint:static + - run: npm run lint:unused:production + - run: npm run lint:unused:full + - run: npm run lint:duplicates + - run: npm run lint:duplicates:full + - run: node quality/generator/generate.mjs + - run: node quality/generator/verify-negative.mjs + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.3" + extensions: curl, mbstring + tools: composer:2.8.12 + - run: composer install --no-interaction --prefer-dist + - run: find lib -name '*.php' -print0 | xargs -0 -n 1 php -l + - run: php quality/contract-smoke.php + - run: git diff --exit-code -- composer.json composer.lock + - uses: actions/upload-artifact@v4 + if: always() + with: + name: quality-evidence + path: | + quality/generator/provenance.json + quality/generator/inventory.json + quality/reports/ + quality-required: + if: always() + needs: [contract] + runs-on: ubuntu-24.04 + steps: + - env: + RESULTS: ${{ toJSON(needs) }} + run: node -e 'const jobs=JSON.parse(process.env.RESULTS); if(!Object.values(jobs).every(job=>job.result==="success")) process.exit(1)' diff --git a/.gitignore b/.gitignore index 9f1681c..f5a35ed 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,10 @@ composer.phar # PHPUnit cache .phpunit.result.cache + +vendor/ + +node_modules/ +quality/reports/ + +.husky/_/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..81e401a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +node tools/quality/node_modules/lint-staged/bin/lint-staged.js diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..60ade1a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.19.0 diff --git a/AGENTS.md b/AGENTS.md index 0104d6c..3537f1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,707 +1,11 @@ -# AGENTS.md — 1msg/sdk +# SDK quality contract -> **Purpose:** exhaustive machine-readable integration guide for AI coding agents. -> **Human docs:** [README.md](./README.md) +This repository implements profile D of 1MSG-156. `php` is the native SDK language; JavaScript rules apply only to authored generator/check tooling. The SDK maintainers own the public generated model/API surface and offline contract fixtures. -## Identity +`quality/generator/openapi.yaml` is pinned to the upstream revision and input hashes in `provenance.json`. OpenAPI Generator 7.22.0 is downloaded with a verified SHA256. Update the contract and its provenance deliberately, then run `node quality/generator/generate.mjs --write`; never hand-edit generated outputs. Every generated code path is replayed and hashed in a temporary directory. New, removed, ignored and modified generated files must match the inventory. Compatibility repairs belong in `quality/postprocess.mjs` and the corresponding upstream producer. -```yaml -package: "1msg/sdk" -registry: https://github.com/1msg/1msg-sdk-php -repository: https://github.com/1msg/1msg-sdk-php -language: PHP -version: 1.1.1 -public_only: true -never_mention: chat-api -``` +Use Node 24.19.0 for tooling and `npm --prefix tools/quality ci`. Run `node tools/quality/run.mjs guards`, `node tools/quality/node_modules/eslint/bin/eslint.js . --max-warnings=0`, `node --test tools/quality/test/*.test.mjs`, generation replay, and the native commands in `.github/workflows/quality.yml`. Native contract fixtures use local transports/serialization only; no live API credentials are needed. Comments describe intent, invariants, failure cases and resource ownership on new/changed functions. -## Decision tree - -```text -Need WhatsApp messaging from PHP? - └─ yes → composer require 1msg/sdk - └─ MessagingApi + Configuration::setHost(baseUrl/instanceId) - ├─ text reply → sendMessage(token, body, chatId|phone) - ├─ file/media → sendFile / uploadMedia - ├─ template (24h closed) → sendTemplate - ├─ interactive list/buttons → sendList / sendButton - ├─ read inbox → listMessages - ├─ profile → getMe - ├─ groups → GroupsApi.* - ├─ flows → FlowsApi.* - └─ templates list → listTemplates -``` - -## Auth invariants - -1. Base path is `{baseUrl}/{instanceId}` (no trailing slash issues). -2. URL: `https://{baseUrl}/{instanceId}/{path}?token={token}`. -3. Pass channel token as the first arg to generated methods. -4. Never log, commit, or expose tokens. -5. Server-side only — no browser usage. - -## Client map - -```php -MessagingApi // send/list messages, media, interactive -ProfileApi // getMe -GroupsApi // create/list/manage groups -FlowsApi // WhatsApp Flows lifecycle -TemplatesApi // listTemplates -ChannelApi // channel helpers -CallingApi // calling -WebhooksApi // webhooks -``` - -## Recipient rules - -- `chatId`: `12020721369@c.us` (1:1) or `...@g.us` (group) -- `phone`: country code + number, no `+` -- Exactly one of `chatId` | `phone` per request - -## Anti-patterns - -- ❌ Raw HTTP without token query param -- ❌ Hardcoding token in source -- ❌ Using sendMessage when 24h window is closed (use sendTemplate) -- ❌ Referencing internal `chat-api` repo name in user-facing code - -## Operation catalog - -### getCallingSettings - -```yaml -operationId: getCallingSettings -http: GET /callingSettings -clientGroup: calling -summary: Get calling settings -description: Return WhatsApp Calling API settings for this channel (beta). Proxies upstream `GET /calling/settings`. **Prerequisites** - Number must be eligible for Meta Calling (Cloud API; not COEX) - Trial / `subscriptionBlocked` channels receive **403** plain text - You need your own WebRTC or SIP stack; 1msg is a **signaling proxy** only and does **not** store call history or recordings See the **Calling** tag overview for inbound/outbound flows and webhooks. -responses: 200, 401, 500 -``` - -### initiateCall - -```yaml -operationId: initiateCall -http: POST /initiateCall -clientGroup: calling -summary: Call action (connect / pre_accept / accept / reject / terminate) -description: Perform a WhatsApp Calling action (beta). Proxies upstream `POST /calling/calls`. Despite the historical path name `/initiateCall`, this endpoint handles **all** call actions: | action | Use | Required | |--------|-----|----------| | `connect` | Outbound business → user | `to` + `session` (`sdp_type: offer`) | | `pre_accept` | Inbound (optional, reduces audio clipping) | `call_id` + `session` (`sdp_type: answer`) | | `accept` | Inbound answer | `call_id` + `session` (`sdp_type: answer`) | | `reject` | Decline inbound | `call_id` | | `terminate` | Hang up | `call_id` | **SDP / media (critical)** - `accept` / `pre_accept` require a **WebRTC-generated SDP answer**. - Do **not** send Meta's offer SDP back as the answer. - Postman (or curl) alone **cannot** establish real media — you need a WebRTC or SIP stack. 1msg only proxies signaling. Answer within ~**30–60 seconds** of an inbound `connect` webhook or Meta terminates as unanswered. Common Meta errors include Calling not enabled (`138000`), no permission (`138006`), SDP validation failures. **Outbound** requires a prior Call Permission Request (CPR) acceptance. See the **Calling** tag overview for the full outbound flow and CPR limits. Trial / `subscriptionBlocked` → **403** plain text. Upstream failures often return HTTP 200 with `{ "response": { "error": "..." } }`. -responses: 200, 401, 500 -``` - -### updateCallingSettings - -```yaml -operationId: updateCallingSettings -http: POST /callingSettings -clientGroup: calling -summary: Update calling settings -description: Enable, disable, or update WhatsApp Calling settings (beta). Proxies upstream `POST /calling/settings`. Body is forwarded as-is (1msg does not validate fields). **Common fields under `calling`** - `status` (`ENABLED` | `DISABLED`) — required to turn calling on/off - `call_icon_visibility` (`DEFAULT` | `DISABLE_ALL`) — optional - `callback_permission_status` (`ENABLED` | `DISABLED`) — optional; when enabled, inbound user calls grant callback permission - `call_hours` — optional hours / timezone object - `sip` — optional SIP trunk; when SIP is ENABLED, Graph call actions and calling webhooks are not used - `srtp_key_exchange_protocol` (`DTLS` | `SDES`) — SDES only with SIP - `video.status` — optional Meta may accept only one feature group per request — prefer focused updates (e.g. enable status first, then SIP). Trial / `subscriptionBlocked` → **403** plain text. -responses: 200, 401, 500 -``` - -### createCommerce - -```yaml -operationId: createCommerce -http: POST /commerce -clientGroup: catalog -summary: Set Commerce Settings -description: Update catalog/cart commerce settings via the `params` object. - `params.is_catalog_visible` — show catalog storefront icon (`true`) or hide it (`false`). - `params.is_cart_enabled` — enable cart (`true`) or disable it (`false`). Blocked when the channel subscription limit is exceeded. Requires a commerce-capable channel (Cloud Functions `/commerceWAV2`). -responses: 200, 401, 500 -``` - -### getCommerce - -```yaml -operationId: getCommerce -http: GET /commerce -clientGroup: catalog -summary: Get Commerce Settings -description: Returns catalog/cart commerce settings for the channel. - `is_catalog_visible` — show catalog storefront icon (`true`) or hide it (`false`). - `is_cart_enabled` — enable cart (`true`) or disable it (`false`). -responses: 200, 401, 500 -``` - -### getConversationalAutomation - -```yaml -operationId: getConversationalAutomation -http: GET /conversationalAutomation -clientGroup: channel -summary: Get conversational automation settings -description: Get WhatsApp conversational components for the channel (welcome message, ice-breaker prompts, and slash commands). Proxies Meta/360dialog `GET /conversational_automation`. When `enable_welcome_message` is true and a user opens chat for the first time, Meta delivers a webhook message with `type: request_welcome`. The inbound formatter exposes that as `type: "request_welcome"` and `meta.request_welcome: true` so your webhook can send a custom welcome reply. -responses: 200, 401, 500 -``` - -### getStatus - -```yaml -operationId: getStatus -http: GET /status -clientGroup: channel -summary: Get channel status -description: Returns WhatsApp Business API client connection status. -responses: 200, 401, 500 -``` - -### setConversationalAutomation - -```yaml -operationId: setConversationalAutomation -http: POST /conversationalAutomation -clientGroup: channel -summary: Set conversational automation settings -description: Update WhatsApp conversational components. Allowed body fields (others are ignored): - `enable_welcome_message` (boolean) - `prompts` (string[], max 4, each ≤ 80 chars) - `commands` (`{ command_name, command_description }[]`) Proxies Meta/360dialog `POST /conversational_automation`. -responses: 200, 401, 500 -``` - -### createFlows - -```yaml -operationId: createFlows -http: POST /flows -clientGroup: flows -summary: Create Flow -responses: 200 -``` - -### createFlowsFlowIdDeprecate - -```yaml -operationId: createFlowsFlowIdDeprecate -http: POST /flows/{flowId}/deprecate -clientGroup: flows -summary: Deprecate Flow -responses: 200 -``` - -### createFlowsFlowIdPublish - -```yaml -operationId: createFlowsFlowIdPublish -http: POST /flows/{flowId}/publish -clientGroup: flows -summary: Publish Flow -responses: 200 -``` - -### deleteFlowsFlowId - -```yaml -operationId: deleteFlowsFlowId -http: DELETE /flows/{flowId} -clientGroup: flows -summary: Delete Flow -responses: 200 -``` - -### getFlowsFlowId - -```yaml -operationId: getFlowsFlowId -http: GET /flows/{flowId} -clientGroup: flows -summary: Get Flow Details -responses: 200 -``` - -### getFlowsFlowIdPreview - -```yaml -operationId: getFlowsFlowIdPreview -http: GET /flows/{flowId}/preview -clientGroup: flows -summary: Preview Flow -responses: 200 -``` - -### getWhatsappBusinessEncryption - -```yaml -operationId: getWhatsappBusinessEncryption -http: GET /whatsapp_business_encryption -clientGroup: flows -summary: Get business encryption public key -description: Retrieve the WhatsApp business public key and signature status for this channel's phone number. Required before publishing or sending Flows that use data encryption. -responses: 200, 401, 500 -``` - -### listFlows - -```yaml -operationId: listFlows -http: GET /flows -clientGroup: flows -summary: List Flows -responses: 200 -``` - -### patchFlowsFlowIdAssets - -```yaml -operationId: patchFlowsFlowIdAssets -http: PATCH /flows/{flowId}/assets -clientGroup: flows -summary: Update Flow Structure -responses: 200 -``` - -### patchFlowsFlowIdMetadata - -```yaml -operationId: patchFlowsFlowIdMetadata -http: PATCH /flows/{flowId}/metadata -clientGroup: flows -summary: Update Flow Metadata -responses: 200 -``` - -### setWhatsappBusinessEncryption - -```yaml -operationId: setWhatsappBusinessEncryption -http: POST /whatsapp_business_encryption -clientGroup: flows -summary: Set business encryption public key -description: Upload and sign a 2048-bit RSA business public key (PEM) for this channel's phone number. Meta requires a signed key before Flow publish/send. Only one active key per number; a new upload replaces the previous key. -responses: 200, 400, 401, 500 -``` - -### createGroups - -```yaml -operationId: createGroups -http: POST /groups -clientGroup: groups -summary: Create Group -responses: 200 -``` - -### createGroupsGroupId - -```yaml -operationId: createGroupsGroupId -http: POST /groups/{groupId} -clientGroup: groups -summary: Update Group Info -responses: 200 -``` - -### createGroupsGroupIdInvitelink - -```yaml -operationId: createGroupsGroupIdInvitelink -http: POST /groups/{groupId}/inviteLink -clientGroup: groups -summary: Reset Invite Link -responses: 200 -``` - -### deleteGroupsGroupId - -```yaml -operationId: deleteGroupsGroupId -http: DELETE /groups/{groupId} -clientGroup: groups -summary: Delete Group -responses: 200 -``` - -### getGroupsGroupId - -```yaml -operationId: getGroupsGroupId -http: GET /groups/{groupId} -clientGroup: groups -summary: Get Group Info -responses: 200 -``` - -### getGroupsGroupIdInvitelink - -```yaml -operationId: getGroupsGroupIdInvitelink -http: GET /groups/{groupId}/inviteLink -clientGroup: groups -summary: Get Invite Link -responses: 200 -``` - -### listGroups - -```yaml -operationId: listGroups -http: GET /groups -clientGroup: groups -summary: Get Groups List -responses: 200 -``` - -### createReadMessage - -```yaml -operationId: createReadMessage -http: POST /readMessage -clientGroup: messaging -summary: Mark message as read -responses: 200, 401 -``` - -### createUploadMedia - -```yaml -operationId: createUploadMedia -http: POST /uploadMedia -clientGroup: messaging -summary: Upload media -responses: 200 -``` - -### deleteMedia - -```yaml -operationId: deleteMedia -http: DELETE /media/{mediaId} -clientGroup: messaging -summary: Delete media from WABA storage -description: Delete previously uploaded media by numeric `mediaId` (from `/uploadMedia`). This is the canonical deletion endpoint and uses the REST `DELETE` verb on the media resource path. The older `POST /deleteMedia` is a deprecated alias. -responses: 200, 400, 401, 500 -``` - -### deleteMediaLegacy - -```yaml -operationId: deleteMediaLegacy -http: POST /deleteMedia -clientGroup: messaging -summary: Delete media from WABA storage (deprecated alias) -description: **Deprecated.** Use `DELETE /media/{mediaId}` instead. This POST alias is kept for backward compatibility with earlier integrations. New integrations should call `DELETE /media/{mediaId}`: 1msg follows REST conventions for resource deletion going forward (delete a resource with the `DELETE` verb on its resource path). -responses: 200, 400, 401, 500 -``` - -### getMmLiteStatus - -```yaml -operationId: getMmLiteStatus -http: GET /mmLiteStatus -clientGroup: messaging -summary: Get MM Lite availability and status -responses: 200, 401, 500 -``` - -### listMessages - -```yaml -operationId: listMessages -http: GET /messages -clientGroup: messaging -summary: Get messages list -responses: 200 -``` - -### retrieveMedia - -```yaml -operationId: retrieveMedia -http: GET /retrieveMedia -clientGroup: messaging -summary: Retrieve uploaded media metadata -description: Get WABA media URL and metadata by mediaId (from uploadMedia). The returned `url` is temporary and typically expires within ~5 minutes. -responses: 200, 400, 401, 500 -``` - -### sendAddressMessage - -```yaml -operationId: sendAddressMessage -http: POST /sendAddressMessage -clientGroup: messaging -summary: Send address request message -description: Request shipping address from the user (WhatsApp interactive `address_message`). **India and Singapore only.** Requires: - Business WhatsApp number registered in that country - Recipient phone matching the country (`+91` ↔ `IN`, `+65` ↔ `SG`) Pass `country: "IN"` or `country: "SG"`. Eligibility is validated upstream; mismatches (e.g. Singapore phone with `country: "IN"`) return errors such as `Unsupported Interactive Message type` (HTTP 200 with `sent: false`). Optional action parameters: `values`, `saved_addresses`, `validation_errors`. -responses: 200, 400, 401, 429, 500 -``` - -### sendButton - -```yaml -operationId: sendButton -http: POST /sendButton -clientGroup: messaging -summary: Send Reply Buttons Message -responses: 200, 400, 401, 429, 500 -``` - -### sendCarousel - -```yaml -operationId: sendCarousel -http: POST /sendCarousel -clientGroup: messaging -summary: Send Carousel -description: You can send product cards via Carousel in two ways: Template messages: do not require a 24-hour customer service window between you and the recipient. Use sendTemplate. Free-form messages: can be sent only when a customer service window is open between you and the recipient. Use sendCarousel. The message structure in /sendCarousel is largely similar to sending a template. However, in this case you must explicitly specify all elements that are created in advance when working with templates. This is because the message is sent without using a template. In /sendCarousel, for sending a Catalog Carousel there can be either 1 URL button or one or more quick reply buttons. -responses: 200, 400, 401, 429, 500 -``` - -### sendContact - -```yaml -operationId: sendContact -http: POST /sendContact -clientGroup: messaging -summary: Send a Contact -responses: 200, 400, 401, 429, 500 -``` - -### sendCtaUrl - -```yaml -operationId: sendCtaUrl -http: POST /sendCtaUrl -clientGroup: messaging -summary: Send CTA URL interactive message -description: Send an interactive message with a single call-to-action URL button. -responses: 200, 400, 401, 429, 500 -``` - -### sendFile - -```yaml -operationId: sendFile -http: POST /sendFile -clientGroup: messaging -summary: Send a File -description: Send a file to an existing chat. (Only if the dialogue has an Open Session). Only one of two parameters is needed to determine the destination - chatId or phone. -responses: 200, 400, 401, 429, 500 -``` - -### sendFlow - -```yaml -operationId: sendFlow -http: POST /sendFlow -clientGroup: messaging -summary: Send WhatsApp Flow Message -description: Send Interactive WhatsApp Flow message to an existing chat. (Only if the dialogue has an Open Session). Only one of two parameters is needed to determine the destination - chatId or phone. Use this method to send a published WhatsApp Flow as a service (interactive) message. If the 24-hour window is closed, send a template with a FLOW button via /sendTemplate. -responses: 200, 400, 401, 429, 500 -``` - -### sendList - -```yaml -operationId: sendList -http: POST /sendList -clientGroup: messaging -summary: Send List Message -responses: 200, 400, 401, 429, 500 -``` - -### sendLocation - -```yaml -operationId: sendLocation -http: POST /sendLocation -clientGroup: messaging -summary: Send a Location -description: Send a location to an existing chat. (Only if the dialogue has an Open Session). Only one of two parameters is needed to determine the destination - chatId or phone. -responses: 200, 400, 401, 429, 500 -``` - -### sendLocationRequest - -```yaml -operationId: sendLocationRequest -http: POST /sendLocationRequest -clientGroup: messaging -summary: Send Location Request Message -responses: 200, 400, 401, 429, 500 -``` - -### sendMessage - -```yaml -operationId: sendMessage -http: POST /sendMessage -clientGroup: messaging -summary: Send a Message -description: Send a message to an existing chat. (Only if the dialogue has an Open Session). The message will be added to the queue for sending and delivered even if the phone is disconnected from the Internet or authorization is not passed. Only one of two parameters is needed to determine the destination - chatId or phone. -responses: 200, 400, 401, 429, 500 -``` - -### sendOrderDetails - -```yaml -operationId: sendOrderDetails -http: POST /sendOrderDetails -clientGroup: messaging -summary: Send order details (India payments template) -description: Send a WhatsApp **order details** payment / invoice message using a pre-approved **Utility** template that has an `ORDER_DETAILS` button. **India only** (WhatsApp Payments India). Requires: - India WhatsApp Business number - Commerce enabled on the channel (`GET`/`POST /commerce`) - Approved template with an `ORDER_DETAILS` button Use this method when you need structured fields (`order`, `referenceId`, `currency`, `paymentSettings`). The API appends a template button `sub_type: order_details` and sends via the same path as `POST /sendTemplate`. Works **outside the 24-hour session window** (template message). You can also send the same payload yourself with `POST /sendTemplate` by including a button component in `params`: ```json { "type": "button", "sub_type": "order_details", "index": 0, "parameters": [{ "type": "action", "action": { "order_details": { "reference_id": "order-123", "currency": "INR", "order": { "status": "pending", "items": [], "subtotal": { "offset": 100, "value": 50000 } } } } }] } ``` -responses: 200, 400, 401, 500 -``` - -### sendPaymentRequest - -```yaml -operationId: sendPaymentRequest -http: POST /sendPaymentRequest -clientGroup: messaging -summary: Send payment request (regional) -description: Send a regional payment request interactive message (beta scaffold). `region` must be IN, SG, or BR. Payload shape follows Meta regional payments docs; verify on stage before production use. Full regional builders are not implemented yet. -responses: 200, 400, 401, 500 -``` - -### sendProduct - -```yaml -operationId: sendProduct -http: POST /sendProduct -clientGroup: messaging -summary: Send a Product -responses: 200, 400, 401, 429, 500 -``` - -### sendReaction - -```yaml -operationId: sendReaction -http: POST /sendReaction -clientGroup: messaging -summary: Send Reaction -responses: 200, 400, 401, 429, 500 -``` - -### sendSticker - -```yaml -operationId: sendSticker -http: POST /sendSticker -clientGroup: messaging -summary: Send sticker message -description: Send a WhatsApp sticker by mediaId or link URL. -responses: 200, 400, 401, 429, 500 -``` - -### getMe - -```yaml -operationId: getMe -http: GET /me -clientGroup: profile -summary: Get Profile Info -description: Get WhatsApp Business Account profile information -responses: 200, 401, 500 -``` - -### updateMe - -```yaml -operationId: updateMe -http: POST /me -clientGroup: profile -summary: Update profile info -description: Update WhatsApp Business Account profile fields. At least one of about, description, email, photo, address, vertical, websites is required. Blocked when the channel subscription limit is exceeded. -responses: 200, 401, 422, 500 -``` - -### addTemplate - -```yaml -operationId: addTemplate -http: POST /addTemplate -clientGroup: templates -summary: Create message template -responses: 200, 401, 500 -``` - -### listTemplates - -```yaml -operationId: listTemplates -http: GET /templates -clientGroup: templates -summary: Get templates list -responses: 200 -``` - -### removeTemplate - -```yaml -operationId: removeTemplate -http: POST /removeTemplate -clientGroup: templates -summary: Remove message template -responses: 200, 401, 500 -``` - -### sendTemplate - -```yaml -operationId: sendTemplate -http: POST /sendTemplate -clientGroup: templates -summary: Send Template Message -description: Send a WhatsApp template message (works outside the 24-hour session window). Supported `params` button `sub_type` values include: `url`, `quick_reply`, `copy_code` / `coupon_code`, `catalog`, `flow`, `limited_time_offer`, and **`order_details`** (WhatsApp Payments **India only** — requires an approved Utility template with an ORDER_DETAILS button). For India order/invoice payments outside 24h, include a button component: ```json { "type": "button", "sub_type": "order_details", "index": 0, "parameters": [{ "type": "action", "action": { "order_details": { "reference_id": "...", "currency": "INR", "order": {} } } }] } ``` Convenience wrapper with structured fields: `POST /sendOrderDetails`. -responses: 200 -``` - -### blockUser - -```yaml -operationId: blockUser -http: POST /blockUser -clientGroup: users -summary: Block WhatsApp user -responses: 200, 400, 401, 500 -``` - -### listBlockedUsers - -```yaml -operationId: listBlockedUsers -http: GET /blockedUsers -clientGroup: users -summary: List blocked WhatsApp users -description: Returns users currently blocked on this WhatsApp channel (WABA `GET /block_users`). Same channel token auth as `blockUser` / `unblockUser`. -responses: 200, 401, 500 -``` - -### unblockUser - -```yaml -operationId: unblockUser -http: POST /unblockUser -clientGroup: users -summary: Unblock WhatsApp user -responses: 200, 400, 401, 500 -``` - -### getWebhook - -```yaml -operationId: getWebhook -http: GET /webhook -clientGroup: webhooks -summary: Get webhook URL -description: Returns the configured client webhook URL for this channel. -responses: 200, 401, 500 -``` - -### setWebhook - -```yaml -operationId: setWebhook -http: POST /webhook -clientGroup: webhooks -summary: Set webhook URL -description: Configure the client webhook URL for inbound events. WhatsApp **Calling** events (`field=calls`) are forwarded as passthrough payloads with `type: "calls"` and `instanceId` (connect / status / terminate). Call permission replies arrive on the normal messages path (`call_permission_reply`). Details: **Calling** tag. -responses: 200, 401, 500 -``` - -## Metadata - -openapi_title: 1MSG WhatsApp Business API (Public) -openapi_version: 1.0.0 -operations: 60 -package: 1msg/sdk -registry: Composer +The `quality-required` aggregate fails if any required job is failed, skipped or cancelled. Repository branch protection must require that check; a workflow file alone does not configure GitHub protection. No generation/build/publish command may swallow errors. +`tools/quality` is a checksum-sealed versioned vendor preset, validated by its own executable tests and integrity check; Knip analyzes the consumer tooling graph and excludes that nested vendor workspace. `java` is the explicitly provisioned native generator runtime (CI Temurin24), not an npm package; its pinned generator JAR checksum is mandatory. No authored consumer source is excluded. diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..6d94e4d --- /dev/null +++ b/composer.lock @@ -0,0 +1,5097 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1d87d892451538e5988b0cc83a20cab8", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "7.15.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.5.3", + "guzzlehttp/psr7": "^2.13.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.15.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:21:06+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.5.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.5.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:11:28+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.13.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "95e7828100de18b4e269fb1703be530082d5166d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/95e7828100de18b4e269fb1703be530082d5166d", + "reference": "95e7828100de18b4e269fb1703be530082d5166d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.13.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:13:11+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:50:05+00:00" + } + ], + "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<2.2.2" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-07T11:47:49+00:00" + }, + { + "name": "composer/semver", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "528948210cc46f967a4b98e83b19e76f2b504993" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/528948210cc46f967a4b98e83b19e76f2b504993", + "reference": "528948210cc46f967a4b98e83b19e76f2b504993", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.5.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-10-05T13:12:12+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", + "shasum": "" + }, + "require": { + "php": "^8.4" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2026-01-05T06:47:08+00:00" + }, + { + "name": "ergebnis/agent-detector", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271", + "shasum": "" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.51.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\AgentDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", + "support": { + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" + }, + "time": "2026-05-07T08:19:07+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "d384a7d0d6e67aa497327260ca95023e92dc2032" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/d384a7d0d6e67aa497327260ca95023e92dc2032", + "reference": "d384a7d0d6e67aa497327260ca95023e92dc2032", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0 || ^1.1.2", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "symfony/polyfill-mbstring": "^1.33", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.4.1" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2026-09-29T22:11:39+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.95.27", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "1d0c68ee5f37446d8ad5970b82384cd1bb0a3317" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1d0c68ee5f37446d8ad5970b82384cd1bb0a3317", + "reference": "1d0c68ee5f37446d8ad5970b82384cd1bb0a3317", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.2", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.37", + "symfony/polyfill-php80": "^1.37", + "symfony/polyfill-php81": "^1.37", + "symfony/polyfill-php84": "^1.37", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.11.0", + "infection/infection": "^0.32.7", + "justinrainbow/json-schema": "^6.12.0", + "keradus/cli-executor": "^2.3", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.35 || ^13.3.3", + "symfony/polyfill-php85": "^1.41", + "symfony/var-dumper": "^5.4.48 || ^6.4.45 || ^7.4.18 || ^8.1.7", + "symfony/yaml": "^5.4.53 || ^6.4.45 || ^7.4.18 || ^8.1.6" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.27" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2026-09-22T09:54:55+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.9.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" + }, + "time": "2026-09-13T18:51:52+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.38", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "8b5f757e8e92071bfdbfa93943f94f84c4e31007" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8b5f757e8e92071bfdbfa93943f94f84c4e31007", + "reference": "8b5f757e8e92071bfdbfa93943f94f84c4e31007", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.10", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.9", + "sebastian/global-state": "^5.0.8", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.38" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-10-05T05:44:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:22:56+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2026-08-11T04:55:59+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:10:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "c85be6922b7fd365942b986b9a50397d65407611" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c85be6922b7fd365942b986b9a50397d65407611", + "reference": "c85be6922b7fd365942b986b9a50397d65407611", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2026-08-11T05:25:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/console", + "version": "v8.1.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "4b81146e3ee248ea969186f85499b54fbc78db08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/4b81146e3ee248ea969186f85499b54fbc78db08", + "reference": "4b81146e3ee248ea969186f85499b54fbc78db08", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v8.1.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-18T16:00:27+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:47:34+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v8.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "7599ebb855fede59413ddb7f15198d67bfcae7ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7599ebb855fede59413ddb7f15198d67bfcae7ba", + "reference": "7599ebb855fede59413ddb7f15198d67bfcae7ba", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-23T10:06:25+00:00" + }, + { + "name": "symfony/finder", + "version": "v8.1.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "bd4fed193a3132c91c5c93595cbfc78988915822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/bd4fed193a3132c91c5c93595cbfc78988915822", + "reference": "bd4fed193a3132c91c5c93595cbfc78988915822", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v8.1.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-26T06:33:49+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-23T22:02:09+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-24T15:19:04+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:45:58+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-23T22:01:09+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "symfony/process", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/10823b09358e690df4ff943e24e8492bb1019fc3", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-02T12:40:29+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-27T15:39:01+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "21c07b026905d596e8379caeb115d87aa479499d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/21c07b026905d596e8379caeb115d87aa479499d", + "reference": "21c07b026905d596e8379caeb115d87aa479499d", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/string", + "version": "v8.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d950140b5f56f31901e5b7a0c04ffc3a3deb943c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d950140b5f56f31901e5b7a0c04ffc3a3deb943c", + "reference": "d950140b5f56f31901e5b7a0c04ffc3a3deb943c", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-11T14:51:16+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..b3efe8c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,13 @@ +import { staticConfig, globals } from "./tools/quality/eslint.mjs"; +export default [ + { + ignores: [ + "**/node_modules/**", + "tools/quality/**", + "vendor/**", + ".build/**", + ".quality-build/**", + ], + }, + ...staticConfig({ globals: globals.node }), +]; diff --git a/knip.json b/knip.json new file mode 100644 index 0000000..97f2c38 --- /dev/null +++ b/knip.json @@ -0,0 +1,12 @@ +{ + "entry": [ + "quality/generator/generate.mjs!", + "quality/generator/verify-negative.mjs!", + "quality/postprocess.mjs!", + "eslint.config.mjs", + "quality.config.mjs" + ], + "project": ["quality/**/*.mjs!", "*.mjs"], + "ignoreBinaries": ["java"], + "ignore": ["tools/quality/**"] +} diff --git a/lib/Api/CallingApi.php b/lib/Api/CallingApi.php index 0638890..bcd554e 100644 --- a/lib/Api/CallingApi.php +++ b/lib/Api/CallingApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Api/CatalogApi.php b/lib/Api/CatalogApi.php index e59ca4c..be3f55c 100644 --- a/lib/Api/CatalogApi.php +++ b/lib/Api/CatalogApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Api/ChannelApi.php b/lib/Api/ChannelApi.php index df62b8b..dd7d7fd 100644 --- a/lib/Api/ChannelApi.php +++ b/lib/Api/ChannelApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -75,12 +75,18 @@ class ChannelApi /** @var string[] $contentTypes **/ public const contentTypes = [ + 'createSettings' => [ + 'application/json', + ], 'getConversationalAutomation' => [ 'application/json', ], 'getStatus' => [ 'application/json', ], + 'listSettings' => [ + 'application/json', + ], 'setConversationalAutomation' => [ 'application/json', ], @@ -133,38 +139,40 @@ public function getConfig() } /** - * Operation getConversationalAutomation + * Operation createSettings * - * Get conversational automation settings + * Update channel settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation + * @param \OneMsgSdk\Model\CreateSettingsRequest $create_settings_request create_settings_request (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createSettings'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return \OneMsgSdk\Model\ConversationalAutomation|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + * @return array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function getConversationalAutomation($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) + public function createSettings($token, $create_settings_request, string $contentType = self::contentTypes['createSettings'][0]) { - list($response) = $this->getConversationalAutomationWithHttpInfo($token, $contentType); + list($response) = $this->createSettingsWithHttpInfo($token, $create_settings_request, $contentType); return $response; } /** - * Operation getConversationalAutomationWithHttpInfo + * Operation createSettingsWithHttpInfo * - * Get conversational automation settings + * Update channel settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation + * @param \OneMsgSdk\Model\CreateSettingsRequest $create_settings_request (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createSettings'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array of \OneMsgSdk\Model\ConversationalAutomation|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + * @return array of array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function getConversationalAutomationWithHttpInfo($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) + public function createSettingsWithHttpInfo($token, $create_settings_request, string $contentType = self::contentTypes['createSettings'][0]) { - $request = $this->getConversationalAutomationRequest($token, $contentType); + $request = $this->createSettingsRequest($token, $create_settings_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -192,7 +200,7 @@ public function getConversationalAutomationWithHttpInfo($token, string $contentT switch($statusCode) { case 200: return $this->handleResponseWithDataType( - '\OneMsgSdk\Model\ConversationalAutomation', + 'array', $request, $response, ); @@ -226,7 +234,7 @@ public function getConversationalAutomationWithHttpInfo($token, string $contentT } return $this->handleResponseWithDataType( - '\OneMsgSdk\Model\ConversationalAutomation', + 'array', $request, $response, ); @@ -235,7 +243,7 @@ public function getConversationalAutomationWithHttpInfo($token, string $contentT case 200: $data = ObjectSerializer::deserialize( $e->getResponseBody(), - '\OneMsgSdk\Model\ConversationalAutomation', + 'array', $e->getResponseHeaders() ); $e->setResponseObject($data); @@ -264,19 +272,20 @@ public function getConversationalAutomationWithHttpInfo($token, string $contentT } /** - * Operation getConversationalAutomationAsync + * Operation createSettingsAsync * - * Get conversational automation settings + * Update channel settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation + * @param \OneMsgSdk\Model\CreateSettingsRequest $create_settings_request (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createSettings'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getConversationalAutomationAsync($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) + public function createSettingsAsync($token, $create_settings_request, string $contentType = self::contentTypes['createSettings'][0]) { - return $this->getConversationalAutomationAsyncWithHttpInfo($token, $contentType) + return $this->createSettingsAsyncWithHttpInfo($token, $create_settings_request, $contentType) ->then( function ($response) { return $response[0]; @@ -285,20 +294,21 @@ function ($response) { } /** - * Operation getConversationalAutomationAsyncWithHttpInfo + * Operation createSettingsAsyncWithHttpInfo * - * Get conversational automation settings + * Update channel settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation + * @param \OneMsgSdk\Model\CreateSettingsRequest $create_settings_request (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createSettings'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getConversationalAutomationAsyncWithHttpInfo($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) + public function createSettingsAsyncWithHttpInfo($token, $create_settings_request, string $contentType = self::contentTypes['createSettings'][0]) { - $returnType = '\OneMsgSdk\Model\ConversationalAutomation'; - $request = $this->getConversationalAutomationRequest($token, $contentType); + $returnType = 'array'; + $request = $this->createSettingsRequest($token, $create_settings_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -337,26 +347,34 @@ function ($exception) { } /** - * Create request for operation 'getConversationalAutomation' + * Create request for operation 'createSettings' * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation + * @param \OneMsgSdk\Model\CreateSettingsRequest $create_settings_request (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createSettings'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function getConversationalAutomationRequest($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) + public function createSettingsRequest($token, $create_settings_request, string $contentType = self::contentTypes['createSettings'][0]) { // verify the required parameter 'token' is set if ($token === null || (is_array($token) && count($token) === 0)) { throw new \InvalidArgumentException( - 'Missing the required parameter $token when calling getConversationalAutomation' + 'Missing the required parameter $token when calling createSettings' ); } + // verify the required parameter 'create_settings_request' is set + if ($create_settings_request === null || (is_array($create_settings_request) && count($create_settings_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $create_settings_request when calling createSettings' + ); + } - $resourcePath = '/conversationalAutomation'; + + $resourcePath = '/settings'; $formParams = []; $queryParams = []; $headerParams = []; @@ -383,7 +401,14 @@ public function getConversationalAutomationRequest($token, string $contentType = ); // for model (json/xml) - if (count($formParams) > 0) { + if (isset($create_settings_request)) { + if (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the body + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($create_settings_request)); + } else { + $httpBody = $create_settings_request; + } + } elseif (count($formParams) > 0) { if ($multipart) { $multipartContents = []; foreach ($formParams as $formParamName => $formParamValue) { @@ -427,7 +452,7 @@ public function getConversationalAutomationRequest($token, string $contentType = $operationHost = $this->config->getHost(); $query = ObjectSerializer::buildQuery($queryParams); return new Request( - 'GET', + 'POST', $operationHost . $resourcePath . ($query ? "?{$query}" : ''), $headers, $httpBody @@ -435,38 +460,38 @@ public function getConversationalAutomationRequest($token, string $contentType = } /** - * Operation getStatus + * Operation getConversationalAutomation * - * Get channel status + * Get conversational automation settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + * @return \OneMsgSdk\Model\ConversationalAutomation|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function getStatus($token, string $contentType = self::contentTypes['getStatus'][0]) + public function getConversationalAutomation($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) { - list($response) = $this->getStatusWithHttpInfo($token, $contentType); + list($response) = $this->getConversationalAutomationWithHttpInfo($token, $contentType); return $response; } /** - * Operation getStatusWithHttpInfo + * Operation getConversationalAutomationWithHttpInfo * - * Get channel status + * Get conversational automation settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array of array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + * @return array of \OneMsgSdk\Model\ConversationalAutomation|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function getStatusWithHttpInfo($token, string $contentType = self::contentTypes['getStatus'][0]) + public function getConversationalAutomationWithHttpInfo($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) { - $request = $this->getStatusRequest($token, $contentType); + $request = $this->getConversationalAutomationRequest($token, $contentType); try { $options = $this->createHttpClientOption(); @@ -494,7 +519,7 @@ public function getStatusWithHttpInfo($token, string $contentType = self::conten switch($statusCode) { case 200: return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\ConversationalAutomation', $request, $response, ); @@ -528,7 +553,7 @@ public function getStatusWithHttpInfo($token, string $contentType = self::conten } return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\ConversationalAutomation', $request, $response, ); @@ -537,7 +562,7 @@ public function getStatusWithHttpInfo($token, string $contentType = self::conten case 200: $data = ObjectSerializer::deserialize( $e->getResponseBody(), - 'array', + '\OneMsgSdk\Model\ConversationalAutomation', $e->getResponseHeaders() ); $e->setResponseObject($data); @@ -566,19 +591,19 @@ public function getStatusWithHttpInfo($token, string $contentType = self::conten } /** - * Operation getStatusAsync + * Operation getConversationalAutomationAsync * - * Get channel status + * Get conversational automation settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getStatusAsync($token, string $contentType = self::contentTypes['getStatus'][0]) + public function getConversationalAutomationAsync($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) { - return $this->getStatusAsyncWithHttpInfo($token, $contentType) + return $this->getConversationalAutomationAsyncWithHttpInfo($token, $contentType) ->then( function ($response) { return $response[0]; @@ -587,20 +612,20 @@ function ($response) { } /** - * Operation getStatusAsyncWithHttpInfo + * Operation getConversationalAutomationAsyncWithHttpInfo * - * Get channel status + * Get conversational automation settings * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getStatusAsyncWithHttpInfo($token, string $contentType = self::contentTypes['getStatus'][0]) + public function getConversationalAutomationAsyncWithHttpInfo($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) { - $returnType = 'array'; - $request = $this->getStatusRequest($token, $contentType); + $returnType = '\OneMsgSdk\Model\ConversationalAutomation'; + $request = $this->getConversationalAutomationRequest($token, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -639,26 +664,630 @@ function ($exception) { } /** - * Create request for operation 'getStatus' + * Create request for operation 'getConversationalAutomation' * * @param string $token JWT token or API key for authorization (required) - * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getConversationalAutomation'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function getStatusRequest($token, string $contentType = self::contentTypes['getStatus'][0]) + public function getConversationalAutomationRequest($token, string $contentType = self::contentTypes['getConversationalAutomation'][0]) { // verify the required parameter 'token' is set if ($token === null || (is_array($token) && count($token) === 0)) { throw new \InvalidArgumentException( - 'Missing the required parameter $token when calling getStatus' + 'Missing the required parameter $token when calling getConversationalAutomation' ); } - $resourcePath = '/status'; + $resourcePath = '/conversationalAutomation'; + $formParams = []; + $queryParams = []; + $headerParams = []; + $httpBody = ''; + $multipart = false; + + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $token, + 'token', // param base name + 'string', // openApiType + 'form', // style + true, // explode + true // required + ) ?? []); + + + + + $headers = $this->headerSelector->selectHeaders( + ['application/json', ], + $contentType, + $multipart + ); + + // for model (json/xml) + if (count($formParams) > 0) { + if ($multipart) { + $multipartContents = []; + foreach ($formParams as $formParamName => $formParamValue) { + $formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue]; + foreach ($formParamValueItems as $formParamValueItem) { + $multipartContents[] = [ + 'name' => $formParamName, + 'contents' => $formParamValueItem + ]; + } + } + // for HTTP post (form) + $httpBody = new MultipartStream($multipartContents); + + } elseif (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the form parameters + $httpBody = \GuzzleHttp\Utils::jsonEncode($formParams); + } else { + // for HTTP post (form) + $httpBody = ObjectSerializer::buildQuery($formParams); + } + } + + // this endpoint requires API key authentication + $apiKey = $this->config->getApiKeyWithPrefix('token'); + if ($apiKey !== null) { + $queryParams['token'] = $apiKey; + } + + $defaultHeaders = []; + if ($this->config->getUserAgent()) { + $defaultHeaders['User-Agent'] = $this->config->getUserAgent(); + } + + $headers = array_merge( + $defaultHeaders, + $headerParams, + $headers + ); + + $operationHost = $this->config->getHost(); + $query = ObjectSerializer::buildQuery($queryParams); + return new Request( + 'GET', + $operationHost . $resourcePath . ($query ? "?{$query}" : ''), + $headers, + $httpBody + ); + } + + /** + * Operation getStatus + * + * Get channel status + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * + * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format + * @throws \InvalidArgumentException + * @return \OneMsgSdk\Model\GetStatus200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + */ + public function getStatus($token, string $contentType = self::contentTypes['getStatus'][0]) + { + list($response) = $this->getStatusWithHttpInfo($token, $contentType); + return $response; + } + + /** + * Operation getStatusWithHttpInfo + * + * Get channel status + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * + * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format + * @throws \InvalidArgumentException + * @return array of \OneMsgSdk\Model\GetStatus200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + */ + public function getStatusWithHttpInfo($token, string $contentType = self::contentTypes['getStatus'][0]) + { + $request = $this->getStatusRequest($token, $contentType); + + try { + $options = $this->createHttpClientOption(); + try { + $response = $this->client->send($request, $options); + } catch (RequestException $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + (int) $e->getCode(), + $e->getResponse() ? $e->getResponse()->getHeaders() : null, + $e->getResponse() ? (string) $e->getResponse()->getBody() : null + ); + } catch (ConnectException $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + (int) $e->getCode(), + null, + null + ); + } + + $statusCode = $response->getStatusCode(); + + + switch($statusCode) { + case 200: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\GetStatus200Response', + $request, + $response, + ); + case 401: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\ErrorResponse', + $request, + $response, + ); + case 500: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\ErrorResponse', + $request, + $response, + ); + } + + + + if ($statusCode < 200 || $statusCode > 299) { + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + (string) $request->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() + ); + } + + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\GetStatus200Response', + $request, + $response, + ); + } catch (ApiException $e) { + switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\GetStatus200Response', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + case 401: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\ErrorResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + case 500: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\ErrorResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + } + + + throw $e; + } + } + + /** + * Operation getStatusAsync + * + * Get channel status + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function getStatusAsync($token, string $contentType = self::contentTypes['getStatus'][0]) + { + return $this->getStatusAsyncWithHttpInfo($token, $contentType) + ->then( + function ($response) { + return $response[0]; + } + ); + } + + /** + * Operation getStatusAsyncWithHttpInfo + * + * Get channel status + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function getStatusAsyncWithHttpInfo($token, string $contentType = self::contentTypes['getStatus'][0]) + { + $returnType = '\OneMsgSdk\Model\GetStatus200Response'; + $request = $this->getStatusRequest($token, $contentType); + + return $this->client + ->sendAsync($request, $this->createHttpClientOption()) + ->then( + function ($response) use ($returnType) { + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + if ($returnType !== 'string') { + $content = json_decode($content); + } + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; + }, + function ($exception) { + $response = $exception->getResponse(); + $statusCode = $response->getStatusCode(); + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + $exception->getRequest()->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() + ); + } + ); + } + + /** + * Create request for operation 'getStatus' + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getStatus'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Psr7\Request + */ + public function getStatusRequest($token, string $contentType = self::contentTypes['getStatus'][0]) + { + + // verify the required parameter 'token' is set + if ($token === null || (is_array($token) && count($token) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $token when calling getStatus' + ); + } + + + $resourcePath = '/status'; + $formParams = []; + $queryParams = []; + $headerParams = []; + $httpBody = ''; + $multipart = false; + + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $token, + 'token', // param base name + 'string', // openApiType + 'form', // style + true, // explode + true // required + ) ?? []); + + + + + $headers = $this->headerSelector->selectHeaders( + ['application/json', ], + $contentType, + $multipart + ); + + // for model (json/xml) + if (count($formParams) > 0) { + if ($multipart) { + $multipartContents = []; + foreach ($formParams as $formParamName => $formParamValue) { + $formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue]; + foreach ($formParamValueItems as $formParamValueItem) { + $multipartContents[] = [ + 'name' => $formParamName, + 'contents' => $formParamValueItem + ]; + } + } + // for HTTP post (form) + $httpBody = new MultipartStream($multipartContents); + + } elseif (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the form parameters + $httpBody = \GuzzleHttp\Utils::jsonEncode($formParams); + } else { + // for HTTP post (form) + $httpBody = ObjectSerializer::buildQuery($formParams); + } + } + + // this endpoint requires API key authentication + $apiKey = $this->config->getApiKeyWithPrefix('token'); + if ($apiKey !== null) { + $queryParams['token'] = $apiKey; + } + + $defaultHeaders = []; + if ($this->config->getUserAgent()) { + $defaultHeaders['User-Agent'] = $this->config->getUserAgent(); + } + + $headers = array_merge( + $defaultHeaders, + $headerParams, + $headers + ); + + $operationHost = $this->config->getHost(); + $query = ObjectSerializer::buildQuery($queryParams); + return new Request( + 'GET', + $operationHost . $resourcePath . ($query ? "?{$query}" : ''), + $headers, + $httpBody + ); + } + + /** + * Operation listSettings + * + * Get channel settings + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listSettings'] to see the possible values for this operation + * + * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format + * @throws \InvalidArgumentException + * @return \OneMsgSdk\Model\ListSettings200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + */ + public function listSettings($token, string $contentType = self::contentTypes['listSettings'][0]) + { + list($response) = $this->listSettingsWithHttpInfo($token, $contentType); + return $response; + } + + /** + * Operation listSettingsWithHttpInfo + * + * Get channel settings + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listSettings'] to see the possible values for this operation + * + * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format + * @throws \InvalidArgumentException + * @return array of \OneMsgSdk\Model\ListSettings200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + */ + public function listSettingsWithHttpInfo($token, string $contentType = self::contentTypes['listSettings'][0]) + { + $request = $this->listSettingsRequest($token, $contentType); + + try { + $options = $this->createHttpClientOption(); + try { + $response = $this->client->send($request, $options); + } catch (RequestException $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + (int) $e->getCode(), + $e->getResponse() ? $e->getResponse()->getHeaders() : null, + $e->getResponse() ? (string) $e->getResponse()->getBody() : null + ); + } catch (ConnectException $e) { + throw new ApiException( + "[{$e->getCode()}] {$e->getMessage()}", + (int) $e->getCode(), + null, + null + ); + } + + $statusCode = $response->getStatusCode(); + + + switch($statusCode) { + case 200: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\ListSettings200Response', + $request, + $response, + ); + case 401: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\ErrorResponse', + $request, + $response, + ); + case 500: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\ErrorResponse', + $request, + $response, + ); + } + + + + if ($statusCode < 200 || $statusCode > 299) { + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + (string) $request->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() + ); + } + + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\ListSettings200Response', + $request, + $response, + ); + } catch (ApiException $e) { + switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\ListSettings200Response', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + case 401: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\ErrorResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + case 500: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\ErrorResponse', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; + } + + + throw $e; + } + } + + /** + * Operation listSettingsAsync + * + * Get channel settings + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listSettings'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function listSettingsAsync($token, string $contentType = self::contentTypes['listSettings'][0]) + { + return $this->listSettingsAsyncWithHttpInfo($token, $contentType) + ->then( + function ($response) { + return $response[0]; + } + ); + } + + /** + * Operation listSettingsAsyncWithHttpInfo + * + * Get channel settings + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listSettings'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function listSettingsAsyncWithHttpInfo($token, string $contentType = self::contentTypes['listSettings'][0]) + { + $returnType = '\OneMsgSdk\Model\ListSettings200Response'; + $request = $this->listSettingsRequest($token, $contentType); + + return $this->client + ->sendAsync($request, $this->createHttpClientOption()) + ->then( + function ($response) use ($returnType) { + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + if ($returnType !== 'string') { + $content = json_decode($content); + } + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; + }, + function ($exception) { + $response = $exception->getResponse(); + $statusCode = $response->getStatusCode(); + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + $exception->getRequest()->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() + ); + } + ); + } + + /** + * Create request for operation 'listSettings' + * + * @param string $token JWT token or API key for authorization (required) + * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listSettings'] to see the possible values for this operation + * + * @throws \InvalidArgumentException + * @return \GuzzleHttp\Psr7\Request + */ + public function listSettingsRequest($token, string $contentType = self::contentTypes['listSettings'][0]) + { + + // verify the required parameter 'token' is set + if ($token === null || (is_array($token) && count($token) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $token when calling listSettings' + ); + } + + + $resourcePath = '/settings'; $formParams = []; $queryParams = []; $headerParams = []; diff --git a/lib/Api/FlowsApi.php b/lib/Api/FlowsApi.php index b1be7b2..34081bf 100644 --- a/lib/Api/FlowsApi.php +++ b/lib/Api/FlowsApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -162,6 +162,7 @@ public function getConfig() * Create Flow * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateFlowsRequest $create_flows_request create_flows_request (required) * @param string|null $waba_account_id waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createFlows'] to see the possible values for this operation * @@ -169,9 +170,9 @@ public function getConfig() * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\CreateGroups200Response */ - public function createFlows($token, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) + public function createFlows($token, $create_flows_request, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) { - list($response) = $this->createFlowsWithHttpInfo($token, $waba_account_id, $contentType); + list($response) = $this->createFlowsWithHttpInfo($token, $create_flows_request, $waba_account_id, $contentType); return $response; } @@ -181,6 +182,7 @@ public function createFlows($token, $waba_account_id = null, string $contentType * Create Flow * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateFlowsRequest $create_flows_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createFlows'] to see the possible values for this operation * @@ -188,9 +190,9 @@ public function createFlows($token, $waba_account_id = null, string $contentType * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\CreateGroups200Response, HTTP status code, HTTP response headers (array of strings) */ - public function createFlowsWithHttpInfo($token, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) + public function createFlowsWithHttpInfo($token, $create_flows_request, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) { - $request = $this->createFlowsRequest($token, $waba_account_id, $contentType); + $request = $this->createFlowsRequest($token, $create_flows_request, $waba_account_id, $contentType); try { $options = $this->createHttpClientOption(); @@ -267,15 +269,16 @@ public function createFlowsWithHttpInfo($token, $waba_account_id = null, string * Create Flow * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateFlowsRequest $create_flows_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createFlows'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createFlowsAsync($token, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) + public function createFlowsAsync($token, $create_flows_request, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) { - return $this->createFlowsAsyncWithHttpInfo($token, $waba_account_id, $contentType) + return $this->createFlowsAsyncWithHttpInfo($token, $create_flows_request, $waba_account_id, $contentType) ->then( function ($response) { return $response[0]; @@ -289,16 +292,17 @@ function ($response) { * Create Flow * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateFlowsRequest $create_flows_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createFlows'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createFlowsAsyncWithHttpInfo($token, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) + public function createFlowsAsyncWithHttpInfo($token, $create_flows_request, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) { $returnType = '\OneMsgSdk\Model\CreateGroups200Response'; - $request = $this->createFlowsRequest($token, $waba_account_id, $contentType); + $request = $this->createFlowsRequest($token, $create_flows_request, $waba_account_id, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -340,13 +344,14 @@ function ($exception) { * Create request for operation 'createFlows' * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateFlowsRequest $create_flows_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createFlows'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function createFlowsRequest($token, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) + public function createFlowsRequest($token, $create_flows_request, $waba_account_id = null, string $contentType = self::contentTypes['createFlows'][0]) { // verify the required parameter 'token' is set @@ -356,6 +361,13 @@ public function createFlowsRequest($token, $waba_account_id = null, string $cont ); } + // verify the required parameter 'create_flows_request' is set + if ($create_flows_request === null || (is_array($create_flows_request) && count($create_flows_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $create_flows_request when calling createFlows' + ); + } + $resourcePath = '/flows'; @@ -394,7 +406,14 @@ public function createFlowsRequest($token, $waba_account_id = null, string $cont ); // for model (json/xml) - if (count($formParams) > 0) { + if (isset($create_flows_request)) { + if (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the body + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($create_flows_request)); + } else { + $httpBody = $create_flows_request; + } + } elseif (count($formParams) > 0) { if ($multipart) { $multipartContents = []; foreach ($formParams as $formParamName => $formParamValue) { @@ -2299,15 +2318,16 @@ public function getWhatsappBusinessEncryptionRequest($token, string $contentType * * @param string $token JWT token or API key for authorization (required) * @param string|null $waba_account_id waba_account_id (optional) + * @param string|null $fields Comma-separated Flow fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listFlows'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\ListFlows200Response */ - public function listFlows($token, $waba_account_id = null, string $contentType = self::contentTypes['listFlows'][0]) + public function listFlows($token, $waba_account_id = null, $fields = null, string $contentType = self::contentTypes['listFlows'][0]) { - list($response) = $this->listFlowsWithHttpInfo($token, $waba_account_id, $contentType); + list($response) = $this->listFlowsWithHttpInfo($token, $waba_account_id, $fields, $contentType); return $response; } @@ -2318,15 +2338,16 @@ public function listFlows($token, $waba_account_id = null, string $contentType = * * @param string $token JWT token or API key for authorization (required) * @param string|null $waba_account_id (optional) + * @param string|null $fields Comma-separated Flow fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listFlows'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\ListFlows200Response, HTTP status code, HTTP response headers (array of strings) */ - public function listFlowsWithHttpInfo($token, $waba_account_id = null, string $contentType = self::contentTypes['listFlows'][0]) + public function listFlowsWithHttpInfo($token, $waba_account_id = null, $fields = null, string $contentType = self::contentTypes['listFlows'][0]) { - $request = $this->listFlowsRequest($token, $waba_account_id, $contentType); + $request = $this->listFlowsRequest($token, $waba_account_id, $fields, $contentType); try { $options = $this->createHttpClientOption(); @@ -2404,14 +2425,15 @@ public function listFlowsWithHttpInfo($token, $waba_account_id = null, string $c * * @param string $token JWT token or API key for authorization (required) * @param string|null $waba_account_id (optional) + * @param string|null $fields Comma-separated Flow fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listFlows'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listFlowsAsync($token, $waba_account_id = null, string $contentType = self::contentTypes['listFlows'][0]) + public function listFlowsAsync($token, $waba_account_id = null, $fields = null, string $contentType = self::contentTypes['listFlows'][0]) { - return $this->listFlowsAsyncWithHttpInfo($token, $waba_account_id, $contentType) + return $this->listFlowsAsyncWithHttpInfo($token, $waba_account_id, $fields, $contentType) ->then( function ($response) { return $response[0]; @@ -2426,15 +2448,16 @@ function ($response) { * * @param string $token JWT token or API key for authorization (required) * @param string|null $waba_account_id (optional) + * @param string|null $fields Comma-separated Flow fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listFlows'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listFlowsAsyncWithHttpInfo($token, $waba_account_id = null, string $contentType = self::contentTypes['listFlows'][0]) + public function listFlowsAsyncWithHttpInfo($token, $waba_account_id = null, $fields = null, string $contentType = self::contentTypes['listFlows'][0]) { $returnType = '\OneMsgSdk\Model\ListFlows200Response'; - $request = $this->listFlowsRequest($token, $waba_account_id, $contentType); + $request = $this->listFlowsRequest($token, $waba_account_id, $fields, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -2477,12 +2500,13 @@ function ($exception) { * * @param string $token JWT token or API key for authorization (required) * @param string|null $waba_account_id (optional) + * @param string|null $fields Comma-separated Flow fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listFlows'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function listFlowsRequest($token, $waba_account_id = null, string $contentType = self::contentTypes['listFlows'][0]) + public function listFlowsRequest($token, $waba_account_id = null, $fields = null, string $contentType = self::contentTypes['listFlows'][0]) { // verify the required parameter 'token' is set @@ -2494,6 +2518,7 @@ public function listFlowsRequest($token, $waba_account_id = null, string $conten + $resourcePath = '/flows'; $formParams = []; $queryParams = []; @@ -2519,6 +2544,15 @@ public function listFlowsRequest($token, $waba_account_id = null, string $conten true, // explode false // required ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $fields, + 'fields', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); @@ -2588,6 +2622,7 @@ public function listFlowsRequest($token, $waba_account_id = null, string $conten * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdAssetsRequest $patch_flows_flow_id_assets_request patch_flows_flow_id_assets_request (required) * @param string|null $waba_account_id waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdAssets'] to see the possible values for this operation * @@ -2595,9 +2630,9 @@ public function listFlowsRequest($token, $waba_account_id = null, string $conten * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\PatchFlowsFlowIdAssets200Response */ - public function patchFlowsFlowIdAssets($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) + public function patchFlowsFlowIdAssets($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) { - list($response) = $this->patchFlowsFlowIdAssetsWithHttpInfo($flow_id, $token, $waba_account_id, $contentType); + list($response) = $this->patchFlowsFlowIdAssetsWithHttpInfo($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id, $contentType); return $response; } @@ -2608,6 +2643,7 @@ public function patchFlowsFlowIdAssets($flow_id, $token, $waba_account_id = null * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdAssetsRequest $patch_flows_flow_id_assets_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdAssets'] to see the possible values for this operation * @@ -2615,9 +2651,9 @@ public function patchFlowsFlowIdAssets($flow_id, $token, $waba_account_id = null * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\PatchFlowsFlowIdAssets200Response, HTTP status code, HTTP response headers (array of strings) */ - public function patchFlowsFlowIdAssetsWithHttpInfo($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) + public function patchFlowsFlowIdAssetsWithHttpInfo($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) { - $request = $this->patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id, $contentType); + $request = $this->patchFlowsFlowIdAssetsRequest($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id, $contentType); try { $options = $this->createHttpClientOption(); @@ -2695,15 +2731,16 @@ public function patchFlowsFlowIdAssetsWithHttpInfo($flow_id, $token, $waba_accou * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdAssetsRequest $patch_flows_flow_id_assets_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdAssets'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function patchFlowsFlowIdAssetsAsync($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) + public function patchFlowsFlowIdAssetsAsync($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) { - return $this->patchFlowsFlowIdAssetsAsyncWithHttpInfo($flow_id, $token, $waba_account_id, $contentType) + return $this->patchFlowsFlowIdAssetsAsyncWithHttpInfo($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id, $contentType) ->then( function ($response) { return $response[0]; @@ -2718,16 +2755,17 @@ function ($response) { * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdAssetsRequest $patch_flows_flow_id_assets_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdAssets'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function patchFlowsFlowIdAssetsAsyncWithHttpInfo($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) + public function patchFlowsFlowIdAssetsAsyncWithHttpInfo($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) { $returnType = '\OneMsgSdk\Model\PatchFlowsFlowIdAssets200Response'; - $request = $this->patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id, $contentType); + $request = $this->patchFlowsFlowIdAssetsRequest($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -2770,13 +2808,14 @@ function ($exception) { * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdAssetsRequest $patch_flows_flow_id_assets_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdAssets'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) + public function patchFlowsFlowIdAssetsRequest($flow_id, $token, $patch_flows_flow_id_assets_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdAssets'][0]) { // verify the required parameter 'flow_id' is set @@ -2793,6 +2832,13 @@ public function patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id ); } + // verify the required parameter 'patch_flows_flow_id_assets_request' is set + if ($patch_flows_flow_id_assets_request === null || (is_array($patch_flows_flow_id_assets_request) && count($patch_flows_flow_id_assets_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $patch_flows_flow_id_assets_request when calling patchFlowsFlowIdAssets' + ); + } + $resourcePath = '/flows/{flowId}/assets'; @@ -2839,7 +2885,14 @@ public function patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id ); // for model (json/xml) - if (count($formParams) > 0) { + if (isset($patch_flows_flow_id_assets_request)) { + if (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the body + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($patch_flows_flow_id_assets_request)); + } else { + $httpBody = $patch_flows_flow_id_assets_request; + } + } elseif (count($formParams) > 0) { if ($multipart) { $multipartContents = []; foreach ($formParams as $formParamName => $formParamValue) { @@ -2897,6 +2950,7 @@ public function patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdMetadataRequest $patch_flows_flow_id_metadata_request patch_flows_flow_id_metadata_request (required) * @param string|null $waba_account_id waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdMetadata'] to see the possible values for this operation * @@ -2904,9 +2958,9 @@ public function patchFlowsFlowIdAssetsRequest($flow_id, $token, $waba_account_id * @throws \InvalidArgumentException * @return array */ - public function patchFlowsFlowIdMetadata($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) + public function patchFlowsFlowIdMetadata($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) { - list($response) = $this->patchFlowsFlowIdMetadataWithHttpInfo($flow_id, $token, $waba_account_id, $contentType); + list($response) = $this->patchFlowsFlowIdMetadataWithHttpInfo($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id, $contentType); return $response; } @@ -2917,6 +2971,7 @@ public function patchFlowsFlowIdMetadata($flow_id, $token, $waba_account_id = nu * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdMetadataRequest $patch_flows_flow_id_metadata_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdMetadata'] to see the possible values for this operation * @@ -2924,9 +2979,9 @@ public function patchFlowsFlowIdMetadata($flow_id, $token, $waba_account_id = nu * @throws \InvalidArgumentException * @return array of array, HTTP status code, HTTP response headers (array of strings) */ - public function patchFlowsFlowIdMetadataWithHttpInfo($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) + public function patchFlowsFlowIdMetadataWithHttpInfo($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) { - $request = $this->patchFlowsFlowIdMetadataRequest($flow_id, $token, $waba_account_id, $contentType); + $request = $this->patchFlowsFlowIdMetadataRequest($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id, $contentType); try { $options = $this->createHttpClientOption(); @@ -3004,15 +3059,16 @@ public function patchFlowsFlowIdMetadataWithHttpInfo($flow_id, $token, $waba_acc * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdMetadataRequest $patch_flows_flow_id_metadata_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdMetadata'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function patchFlowsFlowIdMetadataAsync($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) + public function patchFlowsFlowIdMetadataAsync($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) { - return $this->patchFlowsFlowIdMetadataAsyncWithHttpInfo($flow_id, $token, $waba_account_id, $contentType) + return $this->patchFlowsFlowIdMetadataAsyncWithHttpInfo($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id, $contentType) ->then( function ($response) { return $response[0]; @@ -3027,16 +3083,17 @@ function ($response) { * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdMetadataRequest $patch_flows_flow_id_metadata_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdMetadata'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function patchFlowsFlowIdMetadataAsyncWithHttpInfo($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) + public function patchFlowsFlowIdMetadataAsyncWithHttpInfo($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) { $returnType = 'array'; - $request = $this->patchFlowsFlowIdMetadataRequest($flow_id, $token, $waba_account_id, $contentType); + $request = $this->patchFlowsFlowIdMetadataRequest($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -3079,13 +3136,14 @@ function ($exception) { * * @param string $flow_id Flow ID (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\PatchFlowsFlowIdMetadataRequest $patch_flows_flow_id_metadata_request (required) * @param string|null $waba_account_id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['patchFlowsFlowIdMetadata'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function patchFlowsFlowIdMetadataRequest($flow_id, $token, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) + public function patchFlowsFlowIdMetadataRequest($flow_id, $token, $patch_flows_flow_id_metadata_request, $waba_account_id = null, string $contentType = self::contentTypes['patchFlowsFlowIdMetadata'][0]) { // verify the required parameter 'flow_id' is set @@ -3102,6 +3160,13 @@ public function patchFlowsFlowIdMetadataRequest($flow_id, $token, $waba_account_ ); } + // verify the required parameter 'patch_flows_flow_id_metadata_request' is set + if ($patch_flows_flow_id_metadata_request === null || (is_array($patch_flows_flow_id_metadata_request) && count($patch_flows_flow_id_metadata_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $patch_flows_flow_id_metadata_request when calling patchFlowsFlowIdMetadata' + ); + } + $resourcePath = '/flows/{flowId}/metadata'; @@ -3148,7 +3213,14 @@ public function patchFlowsFlowIdMetadataRequest($flow_id, $token, $waba_account_ ); // for model (json/xml) - if (count($formParams) > 0) { + if (isset($patch_flows_flow_id_metadata_request)) { + if (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the body + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($patch_flows_flow_id_metadata_request)); + } else { + $httpBody = $patch_flows_flow_id_metadata_request; + } + } elseif (count($formParams) > 0) { if ($multipart) { $multipartContents = []; foreach ($formParams as $formParamName => $formParamValue) { diff --git a/lib/Api/GroupsApi.php b/lib/Api/GroupsApi.php index 1201dd1..c676c57 100644 --- a/lib/Api/GroupsApi.php +++ b/lib/Api/GroupsApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -150,15 +150,16 @@ public function getConfig() * Create Group * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsRequest $create_groups_request create_groups_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroups'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\CreateGroups200Response */ - public function createGroups($token, string $contentType = self::contentTypes['createGroups'][0]) + public function createGroups($token, $create_groups_request, string $contentType = self::contentTypes['createGroups'][0]) { - list($response) = $this->createGroupsWithHttpInfo($token, $contentType); + list($response) = $this->createGroupsWithHttpInfo($token, $create_groups_request, $contentType); return $response; } @@ -168,15 +169,16 @@ public function createGroups($token, string $contentType = self::contentTypes['c * Create Group * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsRequest $create_groups_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroups'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\CreateGroups200Response, HTTP status code, HTTP response headers (array of strings) */ - public function createGroupsWithHttpInfo($token, string $contentType = self::contentTypes['createGroups'][0]) + public function createGroupsWithHttpInfo($token, $create_groups_request, string $contentType = self::contentTypes['createGroups'][0]) { - $request = $this->createGroupsRequest($token, $contentType); + $request = $this->createGroupsRequest($token, $create_groups_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -253,14 +255,15 @@ public function createGroupsWithHttpInfo($token, string $contentType = self::con * Create Group * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsRequest $create_groups_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroups'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createGroupsAsync($token, string $contentType = self::contentTypes['createGroups'][0]) + public function createGroupsAsync($token, $create_groups_request, string $contentType = self::contentTypes['createGroups'][0]) { - return $this->createGroupsAsyncWithHttpInfo($token, $contentType) + return $this->createGroupsAsyncWithHttpInfo($token, $create_groups_request, $contentType) ->then( function ($response) { return $response[0]; @@ -274,15 +277,16 @@ function ($response) { * Create Group * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsRequest $create_groups_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroups'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createGroupsAsyncWithHttpInfo($token, string $contentType = self::contentTypes['createGroups'][0]) + public function createGroupsAsyncWithHttpInfo($token, $create_groups_request, string $contentType = self::contentTypes['createGroups'][0]) { $returnType = '\OneMsgSdk\Model\CreateGroups200Response'; - $request = $this->createGroupsRequest($token, $contentType); + $request = $this->createGroupsRequest($token, $create_groups_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -324,12 +328,13 @@ function ($exception) { * Create request for operation 'createGroups' * * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsRequest $create_groups_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroups'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function createGroupsRequest($token, string $contentType = self::contentTypes['createGroups'][0]) + public function createGroupsRequest($token, $create_groups_request, string $contentType = self::contentTypes['createGroups'][0]) { // verify the required parameter 'token' is set @@ -339,6 +344,13 @@ public function createGroupsRequest($token, string $contentType = self::contentT ); } + // verify the required parameter 'create_groups_request' is set + if ($create_groups_request === null || (is_array($create_groups_request) && count($create_groups_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $create_groups_request when calling createGroups' + ); + } + $resourcePath = '/groups'; $formParams = []; @@ -367,7 +379,14 @@ public function createGroupsRequest($token, string $contentType = self::contentT ); // for model (json/xml) - if (count($formParams) > 0) { + if (isset($create_groups_request)) { + if (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the body + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($create_groups_request)); + } else { + $httpBody = $create_groups_request; + } + } elseif (count($formParams) > 0) { if ($multipart) { $multipartContents = []; foreach ($formParams as $formParamName => $formParamValue) { @@ -425,15 +444,16 @@ public function createGroupsRequest($token, string $contentType = self::contentT * * @param string $group_id group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsGroupIdRequest $create_groups_group_id_request create_groups_group_id_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroupsGroupId'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array */ - public function createGroupsGroupId($group_id, $token, string $contentType = self::contentTypes['createGroupsGroupId'][0]) + public function createGroupsGroupId($group_id, $token, $create_groups_group_id_request, string $contentType = self::contentTypes['createGroupsGroupId'][0]) { - list($response) = $this->createGroupsGroupIdWithHttpInfo($group_id, $token, $contentType); + list($response) = $this->createGroupsGroupIdWithHttpInfo($group_id, $token, $create_groups_group_id_request, $contentType); return $response; } @@ -444,15 +464,16 @@ public function createGroupsGroupId($group_id, $token, string $contentType = sel * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsGroupIdRequest $create_groups_group_id_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroupsGroupId'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of array, HTTP status code, HTTP response headers (array of strings) */ - public function createGroupsGroupIdWithHttpInfo($group_id, $token, string $contentType = self::contentTypes['createGroupsGroupId'][0]) + public function createGroupsGroupIdWithHttpInfo($group_id, $token, $create_groups_group_id_request, string $contentType = self::contentTypes['createGroupsGroupId'][0]) { - $request = $this->createGroupsGroupIdRequest($group_id, $token, $contentType); + $request = $this->createGroupsGroupIdRequest($group_id, $token, $create_groups_group_id_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -530,14 +551,15 @@ public function createGroupsGroupIdWithHttpInfo($group_id, $token, string $conte * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsGroupIdRequest $create_groups_group_id_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroupsGroupId'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createGroupsGroupIdAsync($group_id, $token, string $contentType = self::contentTypes['createGroupsGroupId'][0]) + public function createGroupsGroupIdAsync($group_id, $token, $create_groups_group_id_request, string $contentType = self::contentTypes['createGroupsGroupId'][0]) { - return $this->createGroupsGroupIdAsyncWithHttpInfo($group_id, $token, $contentType) + return $this->createGroupsGroupIdAsyncWithHttpInfo($group_id, $token, $create_groups_group_id_request, $contentType) ->then( function ($response) { return $response[0]; @@ -552,15 +574,16 @@ function ($response) { * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsGroupIdRequest $create_groups_group_id_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroupsGroupId'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createGroupsGroupIdAsyncWithHttpInfo($group_id, $token, string $contentType = self::contentTypes['createGroupsGroupId'][0]) + public function createGroupsGroupIdAsyncWithHttpInfo($group_id, $token, $create_groups_group_id_request, string $contentType = self::contentTypes['createGroupsGroupId'][0]) { $returnType = 'array'; - $request = $this->createGroupsGroupIdRequest($group_id, $token, $contentType); + $request = $this->createGroupsGroupIdRequest($group_id, $token, $create_groups_group_id_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -603,12 +626,13 @@ function ($exception) { * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param \OneMsgSdk\Model\CreateGroupsGroupIdRequest $create_groups_group_id_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createGroupsGroupId'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function createGroupsGroupIdRequest($group_id, $token, string $contentType = self::contentTypes['createGroupsGroupId'][0]) + public function createGroupsGroupIdRequest($group_id, $token, $create_groups_group_id_request, string $contentType = self::contentTypes['createGroupsGroupId'][0]) { // verify the required parameter 'group_id' is set @@ -625,6 +649,13 @@ public function createGroupsGroupIdRequest($group_id, $token, string $contentTyp ); } + // verify the required parameter 'create_groups_group_id_request' is set + if ($create_groups_group_id_request === null || (is_array($create_groups_group_id_request) && count($create_groups_group_id_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $create_groups_group_id_request when calling createGroupsGroupId' + ); + } + $resourcePath = '/groups/{groupId}'; $formParams = []; @@ -661,7 +692,14 @@ public function createGroupsGroupIdRequest($group_id, $token, string $contentTyp ); // for model (json/xml) - if (count($formParams) > 0) { + if (isset($create_groups_group_id_request)) { + if (stripos($headers['Content-Type'], 'application/json') !== false) { + # if Content-Type contains "application/json", json_encode the body + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($create_groups_group_id_request)); + } else { + $httpBody = $create_groups_group_id_request; + } + } elseif (count($formParams) > 0) { if ($multipart) { $multipartContents = []; foreach ($formParams as $formParamName => $formParamValue) { @@ -1307,15 +1345,16 @@ public function deleteGroupsGroupIdRequest($group_id, $token, string $contentTyp * * @param string $group_id group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param string|null $fields Comma-separated group fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getGroupsGroupId'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array */ - public function getGroupsGroupId($group_id, $token, string $contentType = self::contentTypes['getGroupsGroupId'][0]) + public function getGroupsGroupId($group_id, $token, $fields = null, string $contentType = self::contentTypes['getGroupsGroupId'][0]) { - list($response) = $this->getGroupsGroupIdWithHttpInfo($group_id, $token, $contentType); + list($response) = $this->getGroupsGroupIdWithHttpInfo($group_id, $token, $fields, $contentType); return $response; } @@ -1326,15 +1365,16 @@ public function getGroupsGroupId($group_id, $token, string $contentType = self:: * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param string|null $fields Comma-separated group fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getGroupsGroupId'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of array, HTTP status code, HTTP response headers (array of strings) */ - public function getGroupsGroupIdWithHttpInfo($group_id, $token, string $contentType = self::contentTypes['getGroupsGroupId'][0]) + public function getGroupsGroupIdWithHttpInfo($group_id, $token, $fields = null, string $contentType = self::contentTypes['getGroupsGroupId'][0]) { - $request = $this->getGroupsGroupIdRequest($group_id, $token, $contentType); + $request = $this->getGroupsGroupIdRequest($group_id, $token, $fields, $contentType); try { $options = $this->createHttpClientOption(); @@ -1412,14 +1452,15 @@ public function getGroupsGroupIdWithHttpInfo($group_id, $token, string $contentT * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param string|null $fields Comma-separated group fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getGroupsGroupId'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getGroupsGroupIdAsync($group_id, $token, string $contentType = self::contentTypes['getGroupsGroupId'][0]) + public function getGroupsGroupIdAsync($group_id, $token, $fields = null, string $contentType = self::contentTypes['getGroupsGroupId'][0]) { - return $this->getGroupsGroupIdAsyncWithHttpInfo($group_id, $token, $contentType) + return $this->getGroupsGroupIdAsyncWithHttpInfo($group_id, $token, $fields, $contentType) ->then( function ($response) { return $response[0]; @@ -1434,15 +1475,16 @@ function ($response) { * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param string|null $fields Comma-separated group fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getGroupsGroupId'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getGroupsGroupIdAsyncWithHttpInfo($group_id, $token, string $contentType = self::contentTypes['getGroupsGroupId'][0]) + public function getGroupsGroupIdAsyncWithHttpInfo($group_id, $token, $fields = null, string $contentType = self::contentTypes['getGroupsGroupId'][0]) { $returnType = 'array'; - $request = $this->getGroupsGroupIdRequest($group_id, $token, $contentType); + $request = $this->getGroupsGroupIdRequest($group_id, $token, $fields, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -1485,12 +1527,13 @@ function ($exception) { * * @param string $group_id (required) * @param string $token JWT token or API key for authorization (required) + * @param string|null $fields Comma-separated group fields to return (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['getGroupsGroupId'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function getGroupsGroupIdRequest($group_id, $token, string $contentType = self::contentTypes['getGroupsGroupId'][0]) + public function getGroupsGroupIdRequest($group_id, $token, $fields = null, string $contentType = self::contentTypes['getGroupsGroupId'][0]) { // verify the required parameter 'group_id' is set @@ -1508,6 +1551,7 @@ public function getGroupsGroupIdRequest($group_id, $token, string $contentType = } + $resourcePath = '/groups/{groupId}'; $formParams = []; $queryParams = []; @@ -1524,6 +1568,15 @@ public function getGroupsGroupIdRequest($group_id, $token, string $contentType = true, // explode true // required ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $fields, + 'fields', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); // path params @@ -1894,15 +1947,18 @@ public function getGroupsGroupIdInvitelinkRequest($group_id, $token, string $con * Get Groups List * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit Max groups to return (optional) + * @param string|null $before Cursor for the previous page (optional) + * @param string|null $after Cursor for the next page (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listGroups'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array */ - public function listGroups($token, string $contentType = self::contentTypes['listGroups'][0]) + public function listGroups($token, $limit = null, $before = null, $after = null, string $contentType = self::contentTypes['listGroups'][0]) { - list($response) = $this->listGroupsWithHttpInfo($token, $contentType); + list($response) = $this->listGroupsWithHttpInfo($token, $limit, $before, $after, $contentType); return $response; } @@ -1912,15 +1968,18 @@ public function listGroups($token, string $contentType = self::contentTypes['lis * Get Groups List * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit Max groups to return (optional) + * @param string|null $before Cursor for the previous page (optional) + * @param string|null $after Cursor for the next page (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listGroups'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of array, HTTP status code, HTTP response headers (array of strings) */ - public function listGroupsWithHttpInfo($token, string $contentType = self::contentTypes['listGroups'][0]) + public function listGroupsWithHttpInfo($token, $limit = null, $before = null, $after = null, string $contentType = self::contentTypes['listGroups'][0]) { - $request = $this->listGroupsRequest($token, $contentType); + $request = $this->listGroupsRequest($token, $limit, $before, $after, $contentType); try { $options = $this->createHttpClientOption(); @@ -1997,14 +2056,17 @@ public function listGroupsWithHttpInfo($token, string $contentType = self::conte * Get Groups List * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit Max groups to return (optional) + * @param string|null $before Cursor for the previous page (optional) + * @param string|null $after Cursor for the next page (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listGroups'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listGroupsAsync($token, string $contentType = self::contentTypes['listGroups'][0]) + public function listGroupsAsync($token, $limit = null, $before = null, $after = null, string $contentType = self::contentTypes['listGroups'][0]) { - return $this->listGroupsAsyncWithHttpInfo($token, $contentType) + return $this->listGroupsAsyncWithHttpInfo($token, $limit, $before, $after, $contentType) ->then( function ($response) { return $response[0]; @@ -2018,15 +2080,18 @@ function ($response) { * Get Groups List * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit Max groups to return (optional) + * @param string|null $before Cursor for the previous page (optional) + * @param string|null $after Cursor for the next page (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listGroups'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listGroupsAsyncWithHttpInfo($token, string $contentType = self::contentTypes['listGroups'][0]) + public function listGroupsAsyncWithHttpInfo($token, $limit = null, $before = null, $after = null, string $contentType = self::contentTypes['listGroups'][0]) { $returnType = 'array'; - $request = $this->listGroupsRequest($token, $contentType); + $request = $this->listGroupsRequest($token, $limit, $before, $after, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -2068,12 +2133,15 @@ function ($exception) { * Create request for operation 'listGroups' * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit Max groups to return (optional) + * @param string|null $before Cursor for the previous page (optional) + * @param string|null $after Cursor for the next page (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listGroups'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function listGroupsRequest($token, string $contentType = self::contentTypes['listGroups'][0]) + public function listGroupsRequest($token, $limit = null, $before = null, $after = null, string $contentType = self::contentTypes['listGroups'][0]) { // verify the required parameter 'token' is set @@ -2084,6 +2152,9 @@ public function listGroupsRequest($token, string $contentType = self::contentTyp } + + + $resourcePath = '/groups'; $formParams = []; $queryParams = []; @@ -2100,6 +2171,33 @@ public function listGroupsRequest($token, string $contentType = self::contentTyp true, // explode true // required ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $limit, + 'limit', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $before, + 'before', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $after, + 'after', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); diff --git a/lib/Api/MessagingApi.php b/lib/Api/MessagingApi.php index fef785b..c90e0fd 100644 --- a/lib/Api/MessagingApi.php +++ b/lib/Api/MessagingApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -521,16 +521,17 @@ public function createReadMessageRequest($token, $message_id = null, $msg_id = n * Upload media * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body request_body (optional) + * @param \OneMsgSdk\Model\CreateUploadMediaRequest $create_upload_media_request create_upload_media_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createUploadMedia'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return void + * @return \OneMsgSdk\Model\CreateUploadMedia200Response */ - public function createUploadMedia($token, $request_body = null, string $contentType = self::contentTypes['createUploadMedia'][0]) + public function createUploadMedia($token, $create_upload_media_request, string $contentType = self::contentTypes['createUploadMedia'][0]) { - $this->createUploadMediaWithHttpInfo($token, $request_body, $contentType); + list($response) = $this->createUploadMediaWithHttpInfo($token, $create_upload_media_request, $contentType); + return $response; } /** @@ -539,16 +540,16 @@ public function createUploadMedia($token, $request_body = null, string $contentT * Upload media * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\CreateUploadMediaRequest $create_upload_media_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createUploadMedia'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array of null, HTTP status code, HTTP response headers (array of strings) + * @return array of \OneMsgSdk\Model\CreateUploadMedia200Response, HTTP status code, HTTP response headers (array of strings) */ - public function createUploadMediaWithHttpInfo($token, $request_body = null, string $contentType = self::contentTypes['createUploadMedia'][0]) + public function createUploadMediaWithHttpInfo($token, $create_upload_media_request, string $contentType = self::contentTypes['createUploadMedia'][0]) { - $request = $this->createUploadMediaRequest($token, $request_body, $contentType); + $request = $this->createUploadMediaRequest($token, $create_upload_media_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -573,9 +574,45 @@ public function createUploadMediaWithHttpInfo($token, $request_body = null, stri $statusCode = $response->getStatusCode(); - return [null, $statusCode, $response->getHeaders()]; + switch($statusCode) { + case 200: + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\CreateUploadMedia200Response', + $request, + $response, + ); + } + + + + if ($statusCode < 200 || $statusCode > 299) { + throw new ApiException( + sprintf( + '[%d] Error connecting to the API (%s)', + $statusCode, + (string) $request->getUri() + ), + $statusCode, + $response->getHeaders(), + (string) $response->getBody() + ); + } + + return $this->handleResponseWithDataType( + '\OneMsgSdk\Model\CreateUploadMedia200Response', + $request, + $response, + ); } catch (ApiException $e) { switch ($e->getCode()) { + case 200: + $data = ObjectSerializer::deserialize( + $e->getResponseBody(), + '\OneMsgSdk\Model\CreateUploadMedia200Response', + $e->getResponseHeaders() + ); + $e->setResponseObject($data); + throw $e; } @@ -589,15 +626,15 @@ public function createUploadMediaWithHttpInfo($token, $request_body = null, stri * Upload media * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\CreateUploadMediaRequest $create_upload_media_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createUploadMedia'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createUploadMediaAsync($token, $request_body = null, string $contentType = self::contentTypes['createUploadMedia'][0]) + public function createUploadMediaAsync($token, $create_upload_media_request, string $contentType = self::contentTypes['createUploadMedia'][0]) { - return $this->createUploadMediaAsyncWithHttpInfo($token, $request_body, $contentType) + return $this->createUploadMediaAsyncWithHttpInfo($token, $create_upload_media_request, $contentType) ->then( function ($response) { return $response[0]; @@ -611,22 +648,35 @@ function ($response) { * Upload media * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\CreateUploadMediaRequest $create_upload_media_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createUploadMedia'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function createUploadMediaAsyncWithHttpInfo($token, $request_body = null, string $contentType = self::contentTypes['createUploadMedia'][0]) + public function createUploadMediaAsyncWithHttpInfo($token, $create_upload_media_request, string $contentType = self::contentTypes['createUploadMedia'][0]) { - $returnType = ''; - $request = $this->createUploadMediaRequest($token, $request_body, $contentType); + $returnType = '\OneMsgSdk\Model\CreateUploadMedia200Response'; + $request = $this->createUploadMediaRequest($token, $create_upload_media_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) ->then( function ($response) use ($returnType) { - return [null, $response->getStatusCode(), $response->getHeaders()]; + if ($returnType === '\SplFileObject') { + $content = $response->getBody(); //stream goes to serializer + } else { + $content = (string) $response->getBody(); + if ($returnType !== 'string') { + $content = json_decode($content); + } + } + + return [ + ObjectSerializer::deserialize($content, $returnType, []), + $response->getStatusCode(), + $response->getHeaders() + ]; }, function ($exception) { $response = $exception->getResponse(); @@ -649,13 +699,13 @@ function ($exception) { * Create request for operation 'createUploadMedia' * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\CreateUploadMediaRequest $create_upload_media_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['createUploadMedia'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function createUploadMediaRequest($token, $request_body = null, string $contentType = self::contentTypes['createUploadMedia'][0]) + public function createUploadMediaRequest($token, $create_upload_media_request, string $contentType = self::contentTypes['createUploadMedia'][0]) { // verify the required parameter 'token' is set @@ -665,6 +715,12 @@ public function createUploadMediaRequest($token, $request_body = null, string $c ); } + // verify the required parameter 'create_upload_media_request' is set + if ($create_upload_media_request === null || (is_array($create_upload_media_request) && count($create_upload_media_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $create_upload_media_request when calling createUploadMedia' + ); + } $resourcePath = '/uploadMedia'; @@ -688,18 +744,18 @@ public function createUploadMediaRequest($token, $request_body = null, string $c $headers = $this->headerSelector->selectHeaders( - [], + ['application/json', ], $contentType, $multipart ); // for model (json/xml) - if (isset($request_body)) { + if (isset($create_upload_media_request)) { if (stripos($headers['Content-Type'], 'application/json') !== false) { # if Content-Type contains "application/json", json_encode the body - $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($request_body)); + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($create_upload_media_request)); } else { - $httpBody = $request_body; + $httpBody = $create_upload_media_request; } } elseif (count($formParams) > 0) { if ($multipart) { @@ -1736,15 +1792,23 @@ public function getMmLiteStatusRequest($token, string $contentType = self::conte * Get messages list * * @param string $token JWT token or API key for authorization (required) + * @param string|null $chat_id Filter messages for one chat (`phone@c.us`, group `@g.us`, or BSUID `@lid`) (optional) + * @param int|null $limit Max rows to return (default 100) (optional) + * @param bool|null $last If true, return the latest messages (DESC). Default false. (optional) + * @param int|null $last_message_number last_message_number (optional) + * @param int|null $first_message_number first_message_number (optional) + * @param int|null $min_time Unix timestamp lower bound (optional) + * @param int|null $max_time Unix timestamp upper bound (optional) + * @param string|null $msg_id Filter by stored message id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listMessages'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\ListMessages200Response */ - public function listMessages($token, string $contentType = self::contentTypes['listMessages'][0]) + public function listMessages($token, $chat_id = null, $limit = null, $last = null, $last_message_number = null, $first_message_number = null, $min_time = null, $max_time = null, $msg_id = null, string $contentType = self::contentTypes['listMessages'][0]) { - list($response) = $this->listMessagesWithHttpInfo($token, $contentType); + list($response) = $this->listMessagesWithHttpInfo($token, $chat_id, $limit, $last, $last_message_number, $first_message_number, $min_time, $max_time, $msg_id, $contentType); return $response; } @@ -1754,15 +1818,23 @@ public function listMessages($token, string $contentType = self::contentTypes['l * Get messages list * * @param string $token JWT token or API key for authorization (required) + * @param string|null $chat_id Filter messages for one chat (`phone@c.us`, group `@g.us`, or BSUID `@lid`) (optional) + * @param int|null $limit Max rows to return (default 100) (optional) + * @param bool|null $last If true, return the latest messages (DESC). Default false. (optional) + * @param int|null $last_message_number (optional) + * @param int|null $first_message_number (optional) + * @param int|null $min_time Unix timestamp lower bound (optional) + * @param int|null $max_time Unix timestamp upper bound (optional) + * @param string|null $msg_id Filter by stored message id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listMessages'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\ListMessages200Response, HTTP status code, HTTP response headers (array of strings) */ - public function listMessagesWithHttpInfo($token, string $contentType = self::contentTypes['listMessages'][0]) + public function listMessagesWithHttpInfo($token, $chat_id = null, $limit = null, $last = null, $last_message_number = null, $first_message_number = null, $min_time = null, $max_time = null, $msg_id = null, string $contentType = self::contentTypes['listMessages'][0]) { - $request = $this->listMessagesRequest($token, $contentType); + $request = $this->listMessagesRequest($token, $chat_id, $limit, $last, $last_message_number, $first_message_number, $min_time, $max_time, $msg_id, $contentType); try { $options = $this->createHttpClientOption(); @@ -1839,14 +1911,22 @@ public function listMessagesWithHttpInfo($token, string $contentType = self::con * Get messages list * * @param string $token JWT token or API key for authorization (required) + * @param string|null $chat_id Filter messages for one chat (`phone@c.us`, group `@g.us`, or BSUID `@lid`) (optional) + * @param int|null $limit Max rows to return (default 100) (optional) + * @param bool|null $last If true, return the latest messages (DESC). Default false. (optional) + * @param int|null $last_message_number (optional) + * @param int|null $first_message_number (optional) + * @param int|null $min_time Unix timestamp lower bound (optional) + * @param int|null $max_time Unix timestamp upper bound (optional) + * @param string|null $msg_id Filter by stored message id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listMessages'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listMessagesAsync($token, string $contentType = self::contentTypes['listMessages'][0]) + public function listMessagesAsync($token, $chat_id = null, $limit = null, $last = null, $last_message_number = null, $first_message_number = null, $min_time = null, $max_time = null, $msg_id = null, string $contentType = self::contentTypes['listMessages'][0]) { - return $this->listMessagesAsyncWithHttpInfo($token, $contentType) + return $this->listMessagesAsyncWithHttpInfo($token, $chat_id, $limit, $last, $last_message_number, $first_message_number, $min_time, $max_time, $msg_id, $contentType) ->then( function ($response) { return $response[0]; @@ -1860,15 +1940,23 @@ function ($response) { * Get messages list * * @param string $token JWT token or API key for authorization (required) + * @param string|null $chat_id Filter messages for one chat (`phone@c.us`, group `@g.us`, or BSUID `@lid`) (optional) + * @param int|null $limit Max rows to return (default 100) (optional) + * @param bool|null $last If true, return the latest messages (DESC). Default false. (optional) + * @param int|null $last_message_number (optional) + * @param int|null $first_message_number (optional) + * @param int|null $min_time Unix timestamp lower bound (optional) + * @param int|null $max_time Unix timestamp upper bound (optional) + * @param string|null $msg_id Filter by stored message id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listMessages'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listMessagesAsyncWithHttpInfo($token, string $contentType = self::contentTypes['listMessages'][0]) + public function listMessagesAsyncWithHttpInfo($token, $chat_id = null, $limit = null, $last = null, $last_message_number = null, $first_message_number = null, $min_time = null, $max_time = null, $msg_id = null, string $contentType = self::contentTypes['listMessages'][0]) { $returnType = '\OneMsgSdk\Model\ListMessages200Response'; - $request = $this->listMessagesRequest($token, $contentType); + $request = $this->listMessagesRequest($token, $chat_id, $limit, $last, $last_message_number, $first_message_number, $min_time, $max_time, $msg_id, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -1910,12 +1998,20 @@ function ($exception) { * Create request for operation 'listMessages' * * @param string $token JWT token or API key for authorization (required) + * @param string|null $chat_id Filter messages for one chat (`phone@c.us`, group `@g.us`, or BSUID `@lid`) (optional) + * @param int|null $limit Max rows to return (default 100) (optional) + * @param bool|null $last If true, return the latest messages (DESC). Default false. (optional) + * @param int|null $last_message_number (optional) + * @param int|null $first_message_number (optional) + * @param int|null $min_time Unix timestamp lower bound (optional) + * @param int|null $max_time Unix timestamp upper bound (optional) + * @param string|null $msg_id Filter by stored message id (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listMessages'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function listMessagesRequest($token, string $contentType = self::contentTypes['listMessages'][0]) + public function listMessagesRequest($token, $chat_id = null, $limit = null, $last = null, $last_message_number = null, $first_message_number = null, $min_time = null, $max_time = null, $msg_id = null, string $contentType = self::contentTypes['listMessages'][0]) { // verify the required parameter 'token' is set @@ -1926,6 +2022,14 @@ public function listMessagesRequest($token, string $contentType = self::contentT } + + + + + + + + $resourcePath = '/messages'; $formParams = []; $queryParams = []; @@ -1942,6 +2046,78 @@ public function listMessagesRequest($token, string $contentType = self::contentT true, // explode true // required ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $chat_id, + 'chatId', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $limit, + 'limit', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $last, + 'last', // param base name + 'boolean', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $last_message_number, + 'lastMessageNumber', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $first_message_number, + 'firstMessageNumber', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $min_time, + 'min_time', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $max_time, + 'max_time', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $msg_id, + 'msgId', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); @@ -3039,8 +3215,9 @@ public function sendButtonRequest($token, $send_button_request = null, string $c * Send Carousel * * @param string $token JWT token or API key for authorization (required) - * @param object[] $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (required) * @param string|null $body Text shown above the carousel. Optional. If omitted and params include a body component, the body will be taken from params. (optional) + * @param array[]|null $cards Preferred. 2–10 carousel cards (header + body + CTA or quick replies). (optional) + * @param object[]|null $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (optional) * @param string|null $quoted_msg_id Quoted message ID (Cloud API) (optional) * @param string|null $chat_id Required if phone is not set. Chat ID from the message list. Examples: 12020721369@c.us or 120363046942338209@g.us(group) (optional) * @param int|null $phone Required if chatId is not set. A phone number starting with the country code. USA example: 12020721369. (optional) @@ -3050,9 +3227,9 @@ public function sendButtonRequest($token, $send_button_request = null, string $c * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\MessageSentResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function sendCarousel($token, $params, $body = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) + public function sendCarousel($token, $body = null, $cards = null, $params = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) { - list($response) = $this->sendCarouselWithHttpInfo($token, $params, $body, $quoted_msg_id, $chat_id, $phone, $contentType); + list($response) = $this->sendCarouselWithHttpInfo($token, $body, $cards, $params, $quoted_msg_id, $chat_id, $phone, $contentType); return $response; } @@ -3062,8 +3239,9 @@ public function sendCarousel($token, $params, $body = null, $quoted_msg_id = nul * Send Carousel * * @param string $token JWT token or API key for authorization (required) - * @param object[] $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (required) * @param string|null $body Text shown above the carousel. Optional. If omitted and params include a body component, the body will be taken from params. (optional) + * @param array[]|null $cards Preferred. 2–10 carousel cards (header + body + CTA or quick replies). (optional) + * @param object[]|null $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (optional) * @param string|null $quoted_msg_id Quoted message ID (Cloud API) (optional) * @param string|null $chat_id Required if phone is not set. Chat ID from the message list. Examples: 12020721369@c.us or 120363046942338209@g.us(group) (optional) * @param int|null $phone Required if chatId is not set. A phone number starting with the country code. USA example: 12020721369. (optional) @@ -3073,9 +3251,9 @@ public function sendCarousel($token, $params, $body = null, $quoted_msg_id = nul * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\MessageSentResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function sendCarouselWithHttpInfo($token, $params, $body = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) + public function sendCarouselWithHttpInfo($token, $body = null, $cards = null, $params = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) { - $request = $this->sendCarouselRequest($token, $params, $body, $quoted_msg_id, $chat_id, $phone, $contentType); + $request = $this->sendCarouselRequest($token, $body, $cards, $params, $quoted_msg_id, $chat_id, $phone, $contentType); try { $options = $this->createHttpClientOption(); @@ -3208,8 +3386,9 @@ public function sendCarouselWithHttpInfo($token, $params, $body = null, $quoted_ * Send Carousel * * @param string $token JWT token or API key for authorization (required) - * @param object[] $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (required) * @param string|null $body Text shown above the carousel. Optional. If omitted and params include a body component, the body will be taken from params. (optional) + * @param array[]|null $cards Preferred. 2–10 carousel cards (header + body + CTA or quick replies). (optional) + * @param object[]|null $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (optional) * @param string|null $quoted_msg_id Quoted message ID (Cloud API) (optional) * @param string|null $chat_id Required if phone is not set. Chat ID from the message list. Examples: 12020721369@c.us or 120363046942338209@g.us(group) (optional) * @param int|null $phone Required if chatId is not set. A phone number starting with the country code. USA example: 12020721369. (optional) @@ -3218,9 +3397,9 @@ public function sendCarouselWithHttpInfo($token, $params, $body = null, $quoted_ * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function sendCarouselAsync($token, $params, $body = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) + public function sendCarouselAsync($token, $body = null, $cards = null, $params = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) { - return $this->sendCarouselAsyncWithHttpInfo($token, $params, $body, $quoted_msg_id, $chat_id, $phone, $contentType) + return $this->sendCarouselAsyncWithHttpInfo($token, $body, $cards, $params, $quoted_msg_id, $chat_id, $phone, $contentType) ->then( function ($response) { return $response[0]; @@ -3234,8 +3413,9 @@ function ($response) { * Send Carousel * * @param string $token JWT token or API key for authorization (required) - * @param object[] $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (required) * @param string|null $body Text shown above the carousel. Optional. If omitted and params include a body component, the body will be taken from params. (optional) + * @param array[]|null $cards Preferred. 2–10 carousel cards (header + body + CTA or quick replies). (optional) + * @param object[]|null $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (optional) * @param string|null $quoted_msg_id Quoted message ID (Cloud API) (optional) * @param string|null $chat_id Required if phone is not set. Chat ID from the message list. Examples: 12020721369@c.us or 120363046942338209@g.us(group) (optional) * @param int|null $phone Required if chatId is not set. A phone number starting with the country code. USA example: 12020721369. (optional) @@ -3244,10 +3424,10 @@ function ($response) { * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function sendCarouselAsyncWithHttpInfo($token, $params, $body = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) + public function sendCarouselAsyncWithHttpInfo($token, $body = null, $cards = null, $params = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) { $returnType = '\OneMsgSdk\Model\MessageSentResponse'; - $request = $this->sendCarouselRequest($token, $params, $body, $quoted_msg_id, $chat_id, $phone, $contentType); + $request = $this->sendCarouselRequest($token, $body, $cards, $params, $quoted_msg_id, $chat_id, $phone, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -3289,8 +3469,9 @@ function ($exception) { * Create request for operation 'sendCarousel' * * @param string $token JWT token or API key for authorization (required) - * @param object[] $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (required) * @param string|null $body Text shown above the carousel. Optional. If omitted and params include a body component, the body will be taken from params. (optional) + * @param array[]|null $cards Preferred. 2–10 carousel cards (header + body + CTA or quick replies). (optional) + * @param object[]|null $params Required. Template-like structure (same as sendTemplate params). Must include a CAROUSEL component and its cards. Structure: - params[] item with type CAROUSEL and cards (2..10) - cards[] item: - components[] items: - header: parameters[0] with type image/video/product - image: {\\\"image\\\": {\\\"link\\\": \\\"https://...\\\"}} - video: {\\\"video\\\": {\\\"link\\\": \\\"https://...\\\"}} - product: {\\\"product\\\": {\\\"catalog_id\\\": \\\"...\\\", \\\"product_retailer_id\\\": \\\"...\\\"}} - button: sub_type url or quick_reply - url: parameters {\\\"text\\\": \\\"Button\\\", \\\"url\\\": \\\"https://...\\\"} - quick_reply: parameters[] of {\\\"id\\\": \\\"...\\\", \\\"text\\\": \\\"...\\\"} (optional) * @param string|null $quoted_msg_id Quoted message ID (Cloud API) (optional) * @param string|null $chat_id Required if phone is not set. Chat ID from the message list. Examples: 12020721369@c.us or 120363046942338209@g.us(group) (optional) * @param int|null $phone Required if chatId is not set. A phone number starting with the country code. USA example: 12020721369. (optional) @@ -3299,7 +3480,7 @@ function ($exception) { * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function sendCarouselRequest($token, $params, $body = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) + public function sendCarouselRequest($token, $body = null, $cards = null, $params = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendCarousel'][0]) { // verify the required parameter 'token' is set @@ -3309,13 +3490,14 @@ public function sendCarouselRequest($token, $params, $body = null, $quoted_msg_i ); } - // verify the required parameter 'params' is set - if ($params === null || (is_array($params) && count($params) === 0)) { - throw new \InvalidArgumentException( - 'Missing the required parameter $params when calling sendCarousel' - ); - } + if ($cards !== null && count($cards) > 10) { + throw new \InvalidArgumentException('invalid value for "$cards" when calling MessagingApi.sendCarousel, number of items must be less than or equal to 10.'); + } + if ($cards !== null && count($cards) < 2) { + throw new \InvalidArgumentException('invalid value for "$cards" when calling MessagingApi.sendCarousel, number of items must be greater than or equal to 2.'); + } + @@ -3345,6 +3527,7 @@ public function sendCarouselRequest($token, $params, $body = null, $quoted_msg_i $formData = $formDataProcessor->prepare([ 'body' => $body, + 'cards' => $cards, 'params' => $params, 'quotedMsgId' => $quoted_msg_id, 'chatId' => $chat_id, @@ -5364,7 +5547,7 @@ public function sendLocation($token, $lat, $lng, $address = null, $name = null, */ public function sendLocationWithHttpInfo($token, $lat, $lng, $address = null, $name = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendLocation'][0]) { - $request = $this->sendLocationRequest($token, $lat, $lng, $address, $name, $quoted_msg_id, $chat_id, $phone, $contentType); + $request = $this->sendLocationHttpRequest($token, $lat, $lng, $address, $name, $quoted_msg_id, $chat_id, $phone, $contentType); try { $options = $this->createHttpClientOption(); @@ -5540,7 +5723,7 @@ function ($response) { public function sendLocationAsyncWithHttpInfo($token, $lat, $lng, $address = null, $name = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendLocation'][0]) { $returnType = '\OneMsgSdk\Model\MessageSentResponse'; - $request = $this->sendLocationRequest($token, $lat, $lng, $address, $name, $quoted_msg_id, $chat_id, $phone, $contentType); + $request = $this->sendLocationHttpRequest($token, $lat, $lng, $address, $name, $quoted_msg_id, $chat_id, $phone, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -5594,7 +5777,7 @@ function ($exception) { * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function sendLocationRequest($token, $lat, $lng, $address = null, $name = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendLocation'][0]) + public function sendLocationHttpRequest($token, $lat, $lng, $address = null, $name = null, $quoted_msg_id = null, $chat_id = null, $phone = null, string $contentType = self::contentTypes['sendLocation'][0]) { // verify the required parameter 'token' is set @@ -6068,7 +6251,7 @@ public function sendLocationRequestRequest($token, $send_location_request_reques * @param string $token JWT token or API key for authorization (required) * @param string $body Message text, UTF-8 or UTF-16 string with emoji (required) * @param string|null $quoted_msg_id Message ID to quote/reply to (Cloud API wamid) (optional) - * @param string|null $chat_id Chat ID in format: phone@c.us (individual) or phone@g.us (group) (optional) + * @param string|null $chat_id Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid (optional) * @param int|null $phone Phone number starting with country code (alternative to chatId) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendMessage'] to see the possible values for this operation * @@ -6090,7 +6273,7 @@ public function sendMessage($token, $body, $quoted_msg_id = null, $chat_id = nul * @param string $token JWT token or API key for authorization (required) * @param string $body Message text, UTF-8 or UTF-16 string with emoji (required) * @param string|null $quoted_msg_id Message ID to quote/reply to (Cloud API wamid) (optional) - * @param string|null $chat_id Chat ID in format: phone@c.us (individual) or phone@g.us (group) (optional) + * @param string|null $chat_id Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid (optional) * @param int|null $phone Phone number starting with country code (alternative to chatId) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendMessage'] to see the possible values for this operation * @@ -6235,7 +6418,7 @@ public function sendMessageWithHttpInfo($token, $body, $quoted_msg_id = null, $c * @param string $token JWT token or API key for authorization (required) * @param string $body Message text, UTF-8 or UTF-16 string with emoji (required) * @param string|null $quoted_msg_id Message ID to quote/reply to (Cloud API wamid) (optional) - * @param string|null $chat_id Chat ID in format: phone@c.us (individual) or phone@g.us (group) (optional) + * @param string|null $chat_id Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid (optional) * @param int|null $phone Phone number starting with country code (alternative to chatId) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendMessage'] to see the possible values for this operation * @@ -6260,7 +6443,7 @@ function ($response) { * @param string $token JWT token or API key for authorization (required) * @param string $body Message text, UTF-8 or UTF-16 string with emoji (required) * @param string|null $quoted_msg_id Message ID to quote/reply to (Cloud API wamid) (optional) - * @param string|null $chat_id Chat ID in format: phone@c.us (individual) or phone@g.us (group) (optional) + * @param string|null $chat_id Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid (optional) * @param int|null $phone Phone number starting with country code (alternative to chatId) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendMessage'] to see the possible values for this operation * @@ -6314,7 +6497,7 @@ function ($exception) { * @param string $token JWT token or API key for authorization (required) * @param string $body Message text, UTF-8 or UTF-16 string with emoji (required) * @param string|null $quoted_msg_id Message ID to quote/reply to (Cloud API wamid) (optional) - * @param string|null $chat_id Chat ID in format: phone@c.us (individual) or phone@g.us (group) (optional) + * @param string|null $chat_id Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid (optional) * @param int|null $phone Phone number starting with country code (alternative to chatId) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendMessage'] to see the possible values for this operation * @@ -6348,10 +6531,7 @@ public function sendMessageRequest($token, $body, $quoted_msg_id = null, $chat_i throw new \InvalidArgumentException("invalid value for \"quoted_msg_id\" when calling MessagingApi.sendMessage, must conform to the pattern /^wamid\\..+$/."); } - if ($chat_id !== null && !preg_match("/^[0-9]+@[cg]\\.us$/", $chat_id)) { - throw new \InvalidArgumentException("invalid value for \"chat_id\" when calling MessagingApi.sendMessage, must conform to the pattern /^[0-9]+@[cg]\\.us$/."); - } - + if ($phone !== null && $phone > 999999999999999) { throw new \InvalidArgumentException('invalid value for "$phone" when calling MessagingApi.sendMessage, must be smaller than or equal to 999999999999999.'); } @@ -7126,16 +7306,16 @@ public function sendPaymentRequestRequest($token, $send_payment_request_request, * Send a Product * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body request_body (optional) + * @param \OneMsgSdk\Model\SendProductRequest $send_product_request send_product_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendProduct'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\MessageSentResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function sendProduct($token, $request_body = null, string $contentType = self::contentTypes['sendProduct'][0]) + public function sendProduct($token, $send_product_request, string $contentType = self::contentTypes['sendProduct'][0]) { - list($response) = $this->sendProductWithHttpInfo($token, $request_body, $contentType); + list($response) = $this->sendProductWithHttpInfo($token, $send_product_request, $contentType); return $response; } @@ -7145,16 +7325,16 @@ public function sendProduct($token, $request_body = null, string $contentType = * Send a Product * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\SendProductRequest $send_product_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendProduct'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\MessageSentResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function sendProductWithHttpInfo($token, $request_body = null, string $contentType = self::contentTypes['sendProduct'][0]) + public function sendProductWithHttpInfo($token, $send_product_request, string $contentType = self::contentTypes['sendProduct'][0]) { - $request = $this->sendProductRequest($token, $request_body, $contentType); + $request = $this->sendProductRequest($token, $send_product_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -7287,15 +7467,15 @@ public function sendProductWithHttpInfo($token, $request_body = null, string $co * Send a Product * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\SendProductRequest $send_product_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendProduct'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function sendProductAsync($token, $request_body = null, string $contentType = self::contentTypes['sendProduct'][0]) + public function sendProductAsync($token, $send_product_request, string $contentType = self::contentTypes['sendProduct'][0]) { - return $this->sendProductAsyncWithHttpInfo($token, $request_body, $contentType) + return $this->sendProductAsyncWithHttpInfo($token, $send_product_request, $contentType) ->then( function ($response) { return $response[0]; @@ -7309,16 +7489,16 @@ function ($response) { * Send a Product * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\SendProductRequest $send_product_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendProduct'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function sendProductAsyncWithHttpInfo($token, $request_body = null, string $contentType = self::contentTypes['sendProduct'][0]) + public function sendProductAsyncWithHttpInfo($token, $send_product_request, string $contentType = self::contentTypes['sendProduct'][0]) { $returnType = '\OneMsgSdk\Model\MessageSentResponse'; - $request = $this->sendProductRequest($token, $request_body, $contentType); + $request = $this->sendProductRequest($token, $send_product_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -7360,13 +7540,13 @@ function ($exception) { * Create request for operation 'sendProduct' * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\SendProductRequest $send_product_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['sendProduct'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function sendProductRequest($token, $request_body = null, string $contentType = self::contentTypes['sendProduct'][0]) + public function sendProductRequest($token, $send_product_request, string $contentType = self::contentTypes['sendProduct'][0]) { // verify the required parameter 'token' is set @@ -7376,6 +7556,12 @@ public function sendProductRequest($token, $request_body = null, string $content ); } + // verify the required parameter 'send_product_request' is set + if ($send_product_request === null || (is_array($send_product_request) && count($send_product_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $send_product_request when calling sendProduct' + ); + } $resourcePath = '/sendProduct'; @@ -7405,12 +7591,12 @@ public function sendProductRequest($token, $request_body = null, string $content ); // for model (json/xml) - if (isset($request_body)) { + if (isset($send_product_request)) { if (stripos($headers['Content-Type'], 'application/json') !== false) { # if Content-Type contains "application/json", json_encode the body - $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($request_body)); + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($send_product_request)); } else { - $httpBody = $request_body; + $httpBody = $send_product_request; } } elseif (count($formParams) > 0) { if ($multipart) { diff --git a/lib/Api/ProfileApi.php b/lib/Api/ProfileApi.php index 73c11ac..d590872 100644 --- a/lib/Api/ProfileApi.php +++ b/lib/Api/ProfileApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -437,14 +437,14 @@ public function getMeRequest($token, string $contentType = self::contentTypes['g * Update profile info * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\UpdateMeRequest|null $update_me_request update_me_request (optional) + * @param \OneMsgSdk\Model\UpdateMeRequest $update_me_request update_me_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['updateMe'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + * @return \OneMsgSdk\Model\UpdateMe200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function updateMe($token, $update_me_request = null, string $contentType = self::contentTypes['updateMe'][0]) + public function updateMe($token, $update_me_request, string $contentType = self::contentTypes['updateMe'][0]) { list($response) = $this->updateMeWithHttpInfo($token, $update_me_request, $contentType); return $response; @@ -456,14 +456,14 @@ public function updateMe($token, $update_me_request = null, string $contentType * Update profile info * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\UpdateMeRequest|null $update_me_request (optional) + * @param \OneMsgSdk\Model\UpdateMeRequest $update_me_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['updateMe'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array of array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + * @return array of \OneMsgSdk\Model\UpdateMe200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function updateMeWithHttpInfo($token, $update_me_request = null, string $contentType = self::contentTypes['updateMe'][0]) + public function updateMeWithHttpInfo($token, $update_me_request, string $contentType = self::contentTypes['updateMe'][0]) { $request = $this->updateMeRequest($token, $update_me_request, $contentType); @@ -493,7 +493,7 @@ public function updateMeWithHttpInfo($token, $update_me_request = null, string $ switch($statusCode) { case 200: return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\UpdateMe200Response', $request, $response, ); @@ -533,7 +533,7 @@ public function updateMeWithHttpInfo($token, $update_me_request = null, string $ } return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\UpdateMe200Response', $request, $response, ); @@ -542,7 +542,7 @@ public function updateMeWithHttpInfo($token, $update_me_request = null, string $ case 200: $data = ObjectSerializer::deserialize( $e->getResponseBody(), - 'array', + '\OneMsgSdk\Model\UpdateMe200Response', $e->getResponseHeaders() ); $e->setResponseObject($data); @@ -584,13 +584,13 @@ public function updateMeWithHttpInfo($token, $update_me_request = null, string $ * Update profile info * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\UpdateMeRequest|null $update_me_request (optional) + * @param \OneMsgSdk\Model\UpdateMeRequest $update_me_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['updateMe'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function updateMeAsync($token, $update_me_request = null, string $contentType = self::contentTypes['updateMe'][0]) + public function updateMeAsync($token, $update_me_request, string $contentType = self::contentTypes['updateMe'][0]) { return $this->updateMeAsyncWithHttpInfo($token, $update_me_request, $contentType) ->then( @@ -606,15 +606,15 @@ function ($response) { * Update profile info * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\UpdateMeRequest|null $update_me_request (optional) + * @param \OneMsgSdk\Model\UpdateMeRequest $update_me_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['updateMe'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function updateMeAsyncWithHttpInfo($token, $update_me_request = null, string $contentType = self::contentTypes['updateMe'][0]) + public function updateMeAsyncWithHttpInfo($token, $update_me_request, string $contentType = self::contentTypes['updateMe'][0]) { - $returnType = 'array'; + $returnType = '\OneMsgSdk\Model\UpdateMe200Response'; $request = $this->updateMeRequest($token, $update_me_request, $contentType); return $this->client @@ -657,13 +657,13 @@ function ($exception) { * Create request for operation 'updateMe' * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\UpdateMeRequest|null $update_me_request (optional) + * @param \OneMsgSdk\Model\UpdateMeRequest $update_me_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['updateMe'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function updateMeRequest($token, $update_me_request = null, string $contentType = self::contentTypes['updateMe'][0]) + public function updateMeRequest($token, $update_me_request, string $contentType = self::contentTypes['updateMe'][0]) { // verify the required parameter 'token' is set @@ -673,6 +673,12 @@ public function updateMeRequest($token, $update_me_request = null, string $conte ); } + // verify the required parameter 'update_me_request' is set + if ($update_me_request === null || (is_array($update_me_request) && count($update_me_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $update_me_request when calling updateMe' + ); + } $resourcePath = '/me'; diff --git a/lib/Api/TemplatesApi.php b/lib/Api/TemplatesApi.php index f0caee9..f4a37e9 100644 --- a/lib/Api/TemplatesApi.php +++ b/lib/Api/TemplatesApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -146,7 +146,7 @@ public function getConfig() * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + * @return \OneMsgSdk\Model\AddTemplate200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ public function addTemplate($token, $add_template_request = null, string $contentType = self::contentTypes['addTemplate'][0]) { @@ -165,7 +165,7 @@ public function addTemplate($token, $add_template_request = null, string $conten * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array of array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + * @return array of \OneMsgSdk\Model\AddTemplate200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ public function addTemplateWithHttpInfo($token, $add_template_request = null, string $contentType = self::contentTypes['addTemplate'][0]) { @@ -197,7 +197,7 @@ public function addTemplateWithHttpInfo($token, $add_template_request = null, st switch($statusCode) { case 200: return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\AddTemplate200Response', $request, $response, ); @@ -231,7 +231,7 @@ public function addTemplateWithHttpInfo($token, $add_template_request = null, st } return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\AddTemplate200Response', $request, $response, ); @@ -240,7 +240,7 @@ public function addTemplateWithHttpInfo($token, $add_template_request = null, st case 200: $data = ObjectSerializer::deserialize( $e->getResponseBody(), - 'array', + '\OneMsgSdk\Model\AddTemplate200Response', $e->getResponseHeaders() ); $e->setResponseObject($data); @@ -304,7 +304,7 @@ function ($response) { */ public function addTemplateAsyncWithHttpInfo($token, $add_template_request = null, string $contentType = self::contentTypes['addTemplate'][0]) { - $returnType = 'array'; + $returnType = '\OneMsgSdk\Model\AddTemplate200Response'; $request = $this->addTemplateRequest($token, $add_template_request, $contentType); return $this->client @@ -456,15 +456,18 @@ public function addTemplateRequest($token, $add_template_request = null, string * Get templates list * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit limit (optional) + * @param int|null $offset offset (optional) + * @param string|null $sort Sort field (`id`, `name`, or `status`) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listTemplates'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return \OneMsgSdk\Model\ListTemplates200Response */ - public function listTemplates($token, string $contentType = self::contentTypes['listTemplates'][0]) + public function listTemplates($token, $limit = null, $offset = null, $sort = null, string $contentType = self::contentTypes['listTemplates'][0]) { - list($response) = $this->listTemplatesWithHttpInfo($token, $contentType); + list($response) = $this->listTemplatesWithHttpInfo($token, $limit, $offset, $sort, $contentType); return $response; } @@ -474,15 +477,18 @@ public function listTemplates($token, string $contentType = self::contentTypes[' * Get templates list * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit (optional) + * @param int|null $offset (optional) + * @param string|null $sort Sort field (`id`, `name`, or `status`) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listTemplates'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of \OneMsgSdk\Model\ListTemplates200Response, HTTP status code, HTTP response headers (array of strings) */ - public function listTemplatesWithHttpInfo($token, string $contentType = self::contentTypes['listTemplates'][0]) + public function listTemplatesWithHttpInfo($token, $limit = null, $offset = null, $sort = null, string $contentType = self::contentTypes['listTemplates'][0]) { - $request = $this->listTemplatesRequest($token, $contentType); + $request = $this->listTemplatesRequest($token, $limit, $offset, $sort, $contentType); try { $options = $this->createHttpClientOption(); @@ -559,14 +565,17 @@ public function listTemplatesWithHttpInfo($token, string $contentType = self::co * Get templates list * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit (optional) + * @param int|null $offset (optional) + * @param string|null $sort Sort field (`id`, `name`, or `status`) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listTemplates'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listTemplatesAsync($token, string $contentType = self::contentTypes['listTemplates'][0]) + public function listTemplatesAsync($token, $limit = null, $offset = null, $sort = null, string $contentType = self::contentTypes['listTemplates'][0]) { - return $this->listTemplatesAsyncWithHttpInfo($token, $contentType) + return $this->listTemplatesAsyncWithHttpInfo($token, $limit, $offset, $sort, $contentType) ->then( function ($response) { return $response[0]; @@ -580,15 +589,18 @@ function ($response) { * Get templates list * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit (optional) + * @param int|null $offset (optional) + * @param string|null $sort Sort field (`id`, `name`, or `status`) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listTemplates'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function listTemplatesAsyncWithHttpInfo($token, string $contentType = self::contentTypes['listTemplates'][0]) + public function listTemplatesAsyncWithHttpInfo($token, $limit = null, $offset = null, $sort = null, string $contentType = self::contentTypes['listTemplates'][0]) { $returnType = '\OneMsgSdk\Model\ListTemplates200Response'; - $request = $this->listTemplatesRequest($token, $contentType); + $request = $this->listTemplatesRequest($token, $limit, $offset, $sort, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -630,12 +642,15 @@ function ($exception) { * Create request for operation 'listTemplates' * * @param string $token JWT token or API key for authorization (required) + * @param int|null $limit (optional) + * @param int|null $offset (optional) + * @param string|null $sort Sort field (`id`, `name`, or `status`) (optional) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['listTemplates'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function listTemplatesRequest($token, string $contentType = self::contentTypes['listTemplates'][0]) + public function listTemplatesRequest($token, $limit = null, $offset = null, $sort = null, string $contentType = self::contentTypes['listTemplates'][0]) { // verify the required parameter 'token' is set @@ -646,6 +661,9 @@ public function listTemplatesRequest($token, string $contentType = self::content } + + + $resourcePath = '/templates'; $formParams = []; $queryParams = []; @@ -662,6 +680,33 @@ public function listTemplatesRequest($token, string $contentType = self::content true, // explode true // required ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $limit, + 'limit', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $offset, + 'offset', // param base name + 'integer', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); + // query params + $queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue( + $sort, + 'sort', // param base name + 'string', // openApiType + 'form', // style + true, // explode + false // required + ) ?? []); @@ -730,16 +775,16 @@ public function listTemplatesRequest($token, string $contentType = self::content * Remove message template * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body request_body (optional) + * @param \OneMsgSdk\Model\RemoveTemplateRequest $remove_template_request remove_template_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['removeTemplate'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function removeTemplate($token, $request_body = null, string $contentType = self::contentTypes['removeTemplate'][0]) + public function removeTemplate($token, $remove_template_request, string $contentType = self::contentTypes['removeTemplate'][0]) { - list($response) = $this->removeTemplateWithHttpInfo($token, $request_body, $contentType); + list($response) = $this->removeTemplateWithHttpInfo($token, $remove_template_request, $contentType); return $response; } @@ -749,16 +794,16 @@ public function removeTemplate($token, $request_body = null, string $contentType * Remove message template * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\RemoveTemplateRequest $remove_template_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['removeTemplate'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException * @return array of array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function removeTemplateWithHttpInfo($token, $request_body = null, string $contentType = self::contentTypes['removeTemplate'][0]) + public function removeTemplateWithHttpInfo($token, $remove_template_request, string $contentType = self::contentTypes['removeTemplate'][0]) { - $request = $this->removeTemplateRequest($token, $request_body, $contentType); + $request = $this->removeTemplateRequest($token, $remove_template_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -863,15 +908,15 @@ public function removeTemplateWithHttpInfo($token, $request_body = null, string * Remove message template * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\RemoveTemplateRequest $remove_template_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['removeTemplate'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function removeTemplateAsync($token, $request_body = null, string $contentType = self::contentTypes['removeTemplate'][0]) + public function removeTemplateAsync($token, $remove_template_request, string $contentType = self::contentTypes['removeTemplate'][0]) { - return $this->removeTemplateAsyncWithHttpInfo($token, $request_body, $contentType) + return $this->removeTemplateAsyncWithHttpInfo($token, $remove_template_request, $contentType) ->then( function ($response) { return $response[0]; @@ -885,16 +930,16 @@ function ($response) { * Remove message template * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\RemoveTemplateRequest $remove_template_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['removeTemplate'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function removeTemplateAsyncWithHttpInfo($token, $request_body = null, string $contentType = self::contentTypes['removeTemplate'][0]) + public function removeTemplateAsyncWithHttpInfo($token, $remove_template_request, string $contentType = self::contentTypes['removeTemplate'][0]) { $returnType = 'array'; - $request = $this->removeTemplateRequest($token, $request_body, $contentType); + $request = $this->removeTemplateRequest($token, $remove_template_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -936,13 +981,13 @@ function ($exception) { * Create request for operation 'removeTemplate' * * @param string $token JWT token or API key for authorization (required) - * @param array|null $request_body (optional) + * @param \OneMsgSdk\Model\RemoveTemplateRequest $remove_template_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['removeTemplate'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function removeTemplateRequest($token, $request_body = null, string $contentType = self::contentTypes['removeTemplate'][0]) + public function removeTemplateRequest($token, $remove_template_request, string $contentType = self::contentTypes['removeTemplate'][0]) { // verify the required parameter 'token' is set @@ -952,6 +997,12 @@ public function removeTemplateRequest($token, $request_body = null, string $cont ); } + // verify the required parameter 'remove_template_request' is set + if ($remove_template_request === null || (is_array($remove_template_request) && count($remove_template_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $remove_template_request when calling removeTemplate' + ); + } $resourcePath = '/removeTemplate'; @@ -981,12 +1032,12 @@ public function removeTemplateRequest($token, $request_body = null, string $cont ); // for model (json/xml) - if (isset($request_body)) { + if (isset($remove_template_request)) { if (stripos($headers['Content-Type'], 'application/json') !== false) { # if Content-Type contains "application/json", json_encode the body - $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($request_body)); + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($remove_template_request)); } else { - $httpBody = $request_body; + $httpBody = $remove_template_request; } } elseif (count($formParams) > 0) { if ($multipart) { diff --git a/lib/Api/UsersApi.php b/lib/Api/UsersApi.php index de7e4ef..077926f 100644 --- a/lib/Api/UsersApi.php +++ b/lib/Api/UsersApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Api/WebhooksApi.php b/lib/Api/WebhooksApi.php index 8768a00..a286c9c 100644 --- a/lib/Api/WebhooksApi.php +++ b/lib/Api/WebhooksApi.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -437,16 +437,16 @@ public function getWebhookRequest($token, string $contentType = self::contentTyp * Set webhook URL * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\GetWebhook200Response|null $get_webhook200_response get_webhook200_response (optional) + * @param \OneMsgSdk\Model\SetWebhookRequest $set_webhook_request set_webhook_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['setWebhook'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse + * @return \OneMsgSdk\Model\SetWebhook200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse */ - public function setWebhook($token, $get_webhook200_response = null, string $contentType = self::contentTypes['setWebhook'][0]) + public function setWebhook($token, $set_webhook_request, string $contentType = self::contentTypes['setWebhook'][0]) { - list($response) = $this->setWebhookWithHttpInfo($token, $get_webhook200_response, $contentType); + list($response) = $this->setWebhookWithHttpInfo($token, $set_webhook_request, $contentType); return $response; } @@ -456,16 +456,16 @@ public function setWebhook($token, $get_webhook200_response = null, string $cont * Set webhook URL * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\GetWebhook200Response|null $get_webhook200_response (optional) + * @param \OneMsgSdk\Model\SetWebhookRequest $set_webhook_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['setWebhook'] to see the possible values for this operation * * @throws \OneMsgSdk\ApiException on non-2xx response or if the response body is not in the expected format * @throws \InvalidArgumentException - * @return array of array|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) + * @return array of \OneMsgSdk\Model\SetWebhook200Response|\OneMsgSdk\Model\ErrorResponse|\OneMsgSdk\Model\ErrorResponse, HTTP status code, HTTP response headers (array of strings) */ - public function setWebhookWithHttpInfo($token, $get_webhook200_response = null, string $contentType = self::contentTypes['setWebhook'][0]) + public function setWebhookWithHttpInfo($token, $set_webhook_request, string $contentType = self::contentTypes['setWebhook'][0]) { - $request = $this->setWebhookRequest($token, $get_webhook200_response, $contentType); + $request = $this->setWebhookRequest($token, $set_webhook_request, $contentType); try { $options = $this->createHttpClientOption(); @@ -493,7 +493,7 @@ public function setWebhookWithHttpInfo($token, $get_webhook200_response = null, switch($statusCode) { case 200: return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\SetWebhook200Response', $request, $response, ); @@ -527,7 +527,7 @@ public function setWebhookWithHttpInfo($token, $get_webhook200_response = null, } return $this->handleResponseWithDataType( - 'array', + '\OneMsgSdk\Model\SetWebhook200Response', $request, $response, ); @@ -536,7 +536,7 @@ public function setWebhookWithHttpInfo($token, $get_webhook200_response = null, case 200: $data = ObjectSerializer::deserialize( $e->getResponseBody(), - 'array', + '\OneMsgSdk\Model\SetWebhook200Response', $e->getResponseHeaders() ); $e->setResponseObject($data); @@ -570,15 +570,15 @@ public function setWebhookWithHttpInfo($token, $get_webhook200_response = null, * Set webhook URL * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\GetWebhook200Response|null $get_webhook200_response (optional) + * @param \OneMsgSdk\Model\SetWebhookRequest $set_webhook_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['setWebhook'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function setWebhookAsync($token, $get_webhook200_response = null, string $contentType = self::contentTypes['setWebhook'][0]) + public function setWebhookAsync($token, $set_webhook_request, string $contentType = self::contentTypes['setWebhook'][0]) { - return $this->setWebhookAsyncWithHttpInfo($token, $get_webhook200_response, $contentType) + return $this->setWebhookAsyncWithHttpInfo($token, $set_webhook_request, $contentType) ->then( function ($response) { return $response[0]; @@ -592,16 +592,16 @@ function ($response) { * Set webhook URL * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\GetWebhook200Response|null $get_webhook200_response (optional) + * @param \OneMsgSdk\Model\SetWebhookRequest $set_webhook_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['setWebhook'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Promise\PromiseInterface */ - public function setWebhookAsyncWithHttpInfo($token, $get_webhook200_response = null, string $contentType = self::contentTypes['setWebhook'][0]) + public function setWebhookAsyncWithHttpInfo($token, $set_webhook_request, string $contentType = self::contentTypes['setWebhook'][0]) { - $returnType = 'array'; - $request = $this->setWebhookRequest($token, $get_webhook200_response, $contentType); + $returnType = '\OneMsgSdk\Model\SetWebhook200Response'; + $request = $this->setWebhookRequest($token, $set_webhook_request, $contentType); return $this->client ->sendAsync($request, $this->createHttpClientOption()) @@ -643,13 +643,13 @@ function ($exception) { * Create request for operation 'setWebhook' * * @param string $token JWT token or API key for authorization (required) - * @param \OneMsgSdk\Model\GetWebhook200Response|null $get_webhook200_response (optional) + * @param \OneMsgSdk\Model\SetWebhookRequest $set_webhook_request (required) * @param string $contentType The value for the Content-Type header. Check self::contentTypes['setWebhook'] to see the possible values for this operation * * @throws \InvalidArgumentException * @return \GuzzleHttp\Psr7\Request */ - public function setWebhookRequest($token, $get_webhook200_response = null, string $contentType = self::contentTypes['setWebhook'][0]) + public function setWebhookRequest($token, $set_webhook_request, string $contentType = self::contentTypes['setWebhook'][0]) { // verify the required parameter 'token' is set @@ -659,6 +659,12 @@ public function setWebhookRequest($token, $get_webhook200_response = null, strin ); } + // verify the required parameter 'set_webhook_request' is set + if ($set_webhook_request === null || (is_array($set_webhook_request) && count($set_webhook_request) === 0)) { + throw new \InvalidArgumentException( + 'Missing the required parameter $set_webhook_request when calling setWebhook' + ); + } $resourcePath = '/webhook'; @@ -688,12 +694,12 @@ public function setWebhookRequest($token, $get_webhook200_response = null, strin ); // for model (json/xml) - if (isset($get_webhook200_response)) { + if (isset($set_webhook_request)) { if (stripos($headers['Content-Type'], 'application/json') !== false) { # if Content-Type contains "application/json", json_encode the body - $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($get_webhook200_response)); + $httpBody = \GuzzleHttp\Utils::jsonEncode(ObjectSerializer::sanitizeForSerialization($set_webhook_request)); } else { - $httpBody = $get_webhook200_response; + $httpBody = $set_webhook_request; } } elseif (count($formParams) > 0) { if ($multipart) { diff --git a/lib/ApiException.php b/lib/ApiException.php index 8419e05..cc25f7f 100644 --- a/lib/ApiException.php +++ b/lib/ApiException.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Configuration.php b/lib/Configuration.php index ec77123..a0a6994 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/FormDataProcessor.php b/lib/FormDataProcessor.php index 56ccf24..b17be28 100644 --- a/lib/FormDataProcessor.php +++ b/lib/FormDataProcessor.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/HeaderSelector.php b/lib/HeaderSelector.php index 7c52084..30ce102 100644 --- a/lib/HeaderSelector.php +++ b/lib/HeaderSelector.php @@ -12,7 +12,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/AddTemplate200Response.php b/lib/Model/AddTemplate200Response.php new file mode 100644 index 0000000..397d3d3 --- /dev/null +++ b/lib/Model/AddTemplate200Response.php @@ -0,0 +1,410 @@ + + */ +class AddTemplate200Response implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'addTemplate_200_response'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'response' => 'array' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'response' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'response' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'response' => 'response' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'response' => 'setResponse' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'response' => 'getResponse' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('response', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets response + * + * @return array|null + */ + public function getResponse() + { + return $this->container['response']; + } + + /** + * Sets response + * + * @param array|null $response Upstream create-template payload + * + * @return self + */ + public function setResponse($response) + { + if (is_null($response)) { + throw new \InvalidArgumentException('non-nullable response cannot be null'); + } + $this->container['response'] = $response; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/AddTemplateRequest.php b/lib/Model/AddTemplateRequest.php index 786252e..3cb69e1 100644 --- a/lib/Model/AddTemplateRequest.php +++ b/lib/Model/AddTemplateRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -324,7 +324,7 @@ public function getName() /** * Sets name * - * @param string|null $name name + * @param string|null $name Template name * * @return self */ @@ -351,7 +351,7 @@ public function getCategory() /** * Sets category * - * @param string|null $category category + * @param string|null $category MARKETING, UTILITY, or AUTHENTICATION * * @return self */ @@ -378,7 +378,7 @@ public function getLanguage() /** * Sets language * - * @param string|null $language language + * @param string|null $language Template language code (e.g. en_US) * * @return self */ @@ -405,7 +405,7 @@ public function getComponents() /** * Sets components * - * @param array[]|null $components components + * @param array[]|null $components Template components (HEADER, BODY, FOOTER, BUTTONS, CAROUSEL) * * @return self */ diff --git a/lib/Model/BlockUserRequest.php b/lib/Model/BlockUserRequest.php index 4e4e1cb..ebfb814 100644 --- a/lib/Model/BlockUserRequest.php +++ b/lib/Model/BlockUserRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettings.php b/lib/Model/CallingSettings.php index b83c714..418ce57 100644 --- a/lib/Model/CallingSettings.php +++ b/lib/Model/CallingSettings.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettingsCalling.php b/lib/Model/CallingSettingsCalling.php index e5e091d..0ed1501 100644 --- a/lib/Model/CallingSettingsCalling.php +++ b/lib/Model/CallingSettingsCalling.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettingsCallingAudio.php b/lib/Model/CallingSettingsCallingAudio.php index e48c9e8..4626efc 100644 --- a/lib/Model/CallingSettingsCallingAudio.php +++ b/lib/Model/CallingSettingsCallingAudio.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettingsCallingCallHours.php b/lib/Model/CallingSettingsCallingCallHours.php index d691428..41eab2c 100644 --- a/lib/Model/CallingSettingsCallingCallHours.php +++ b/lib/Model/CallingSettingsCallingCallHours.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettingsCallingSip.php b/lib/Model/CallingSettingsCallingSip.php index b1fdd5a..e606991 100644 --- a/lib/Model/CallingSettingsCallingSip.php +++ b/lib/Model/CallingSettingsCallingSip.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettingsCallingSipServersInner.php b/lib/Model/CallingSettingsCallingSipServersInner.php index df825b1..91b6f59 100644 --- a/lib/Model/CallingSettingsCallingSipServersInner.php +++ b/lib/Model/CallingSettingsCallingSipServersInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CallingSettingsCallingVideo.php b/lib/Model/CallingSettingsCallingVideo.php index 45d7565..7ef916e 100644 --- a/lib/Model/CallingSettingsCallingVideo.php +++ b/lib/Model/CallingSettingsCallingVideo.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ConversationalAutomation.php b/lib/Model/ConversationalAutomation.php index c4bb9ed..94b847b 100644 --- a/lib/Model/ConversationalAutomation.php +++ b/lib/Model/ConversationalAutomation.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ConversationalAutomationCommandsInner.php b/lib/Model/ConversationalAutomationCommandsInner.php index 30baa05..2c6f12b 100644 --- a/lib/Model/ConversationalAutomationCommandsInner.php +++ b/lib/Model/ConversationalAutomationCommandsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateCommerce200Response.php b/lib/Model/CreateCommerce200Response.php index 091576d..459ae8e 100644 --- a/lib/Model/CreateCommerce200Response.php +++ b/lib/Model/CreateCommerce200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateCommerceRequest.php b/lib/Model/CreateCommerceRequest.php index 3f78c40..be1818c 100644 --- a/lib/Model/CreateCommerceRequest.php +++ b/lib/Model/CreateCommerceRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateCommerceRequestParams.php b/lib/Model/CreateCommerceRequestParams.php index 88e8513..c8d4248 100644 --- a/lib/Model/CreateCommerceRequestParams.php +++ b/lib/Model/CreateCommerceRequestParams.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateFlowsRequest.php b/lib/Model/CreateFlowsRequest.php new file mode 100644 index 0000000..b8074b1 --- /dev/null +++ b/lib/Model/CreateFlowsRequest.php @@ -0,0 +1,588 @@ + + */ +class CreateFlowsRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createFlows_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'name' => 'string', + 'categories' => 'string[]', + 'endpoint_uri' => 'string', + 'publish' => 'bool', + 'flow_json' => 'array' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'name' => null, + 'categories' => null, + 'endpoint_uri' => null, + 'publish' => null, + 'flow_json' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'name' => false, + 'categories' => false, + 'endpoint_uri' => false, + 'publish' => false, + 'flow_json' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'name' => 'name', + 'categories' => 'categories', + 'endpoint_uri' => 'endpointUri', + 'publish' => 'publish', + 'flow_json' => 'flowJson' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'name' => 'setName', + 'categories' => 'setCategories', + 'endpoint_uri' => 'setEndpointUri', + 'publish' => 'setPublish', + 'flow_json' => 'setFlowJson' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'name' => 'getName', + 'categories' => 'getCategories', + 'endpoint_uri' => 'getEndpointUri', + 'publish' => 'getPublish', + 'flow_json' => 'getFlowJson' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const CATEGORIES_SIGN_UP = 'SIGN_UP'; + public const CATEGORIES_SIGN_IN = 'SIGN_IN'; + public const CATEGORIES_APPOINTMENT_BOOKING = 'APPOINTMENT_BOOKING'; + public const CATEGORIES_LEAD_GENERATION = 'LEAD_GENERATION'; + public const CATEGORIES_CONTACT_US = 'CONTACT_US'; + public const CATEGORIES_CUSTOMER_SUPPORT = 'CUSTOMER_SUPPORT'; + public const CATEGORIES_SURVEY = 'SURVEY'; + public const CATEGORIES_OTHER = 'OTHER'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getCategoriesAllowableValues() + { + return [ + self::CATEGORIES_SIGN_UP, + self::CATEGORIES_SIGN_IN, + self::CATEGORIES_APPOINTMENT_BOOKING, + self::CATEGORIES_LEAD_GENERATION, + self::CATEGORIES_CONTACT_US, + self::CATEGORIES_CUSTOMER_SUPPORT, + self::CATEGORIES_SURVEY, + self::CATEGORIES_OTHER, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('name', $data ?? [], null); + $this->setIfExists('categories', $data ?? [], null); + $this->setIfExists('endpoint_uri', $data ?? [], null); + $this->setIfExists('publish', $data ?? [], null); + $this->setIfExists('flow_json', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['name'] === null) { + $invalidProperties[] = "'name' can't be null"; + } + if ($this->container['categories'] === null) { + $invalidProperties[] = "'categories' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets name + * + * @return string + */ + public function getName() + { + return $this->container['name']; + } + + /** + * Sets name + * + * @param string $name Flow name + * + * @return self + */ + public function setName($name) + { + if (is_null($name)) { + throw new \InvalidArgumentException('non-nullable name cannot be null'); + } + $this->container['name'] = $name; + + return $this; + } + + /** + * Gets categories + * + * @return string[] + */ + public function getCategories() + { + return $this->container['categories']; + } + + /** + * Sets categories + * + * @param string[] $categories One or more Flow categories + * + * @return self + */ + public function setCategories($categories) + { + if (is_null($categories)) { + throw new \InvalidArgumentException('non-nullable categories cannot be null'); + } + $allowedValues = $this->getCategoriesAllowableValues(); + if (array_diff($categories, $allowedValues)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value for 'categories', must be one of '%s'", + implode("', '", $allowedValues) + ) + ); + } + $this->container['categories'] = $categories; + + return $this; + } + + /** + * Gets endpoint_uri + * + * @return string|null + */ + public function getEndpointUri() + { + return $this->container['endpoint_uri']; + } + + /** + * Sets endpoint_uri + * + * @param string|null $endpoint_uri HTTPS endpoint for the Flow data channel + * + * @return self + */ + public function setEndpointUri($endpoint_uri) + { + if (is_null($endpoint_uri)) { + throw new \InvalidArgumentException('non-nullable endpoint_uri cannot be null'); + } + $this->container['endpoint_uri'] = $endpoint_uri; + + return $this; + } + + /** + * Gets publish + * + * @return bool|null + */ + public function getPublish() + { + return $this->container['publish']; + } + + /** + * Sets publish + * + * @param bool|null $publish Publish immediately after create + * + * @return self + */ + public function setPublish($publish) + { + if (is_null($publish)) { + throw new \InvalidArgumentException('non-nullable publish cannot be null'); + } + $this->container['publish'] = $publish; + + return $this; + } + + /** + * Gets flow_json + * + * @return array|null + */ + public function getFlowJson() + { + return $this->container['flow_json']; + } + + /** + * Sets flow_json + * + * @param array|null $flow_json Optional Flow JSON document + * + * @return self + */ + public function setFlowJson($flow_json) + { + if (is_null($flow_json)) { + throw new \InvalidArgumentException('non-nullable flow_json cannot be null'); + } + $this->container['flow_json'] = $flow_json; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/CreateGroups200Response.php b/lib/Model/CreateGroups200Response.php index 7330b85..b188ddb 100644 --- a/lib/Model/CreateGroups200Response.php +++ b/lib/Model/CreateGroups200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateGroupsGroupIdRequest.php b/lib/Model/CreateGroupsGroupIdRequest.php new file mode 100644 index 0000000..8633a3c --- /dev/null +++ b/lib/Model/CreateGroupsGroupIdRequest.php @@ -0,0 +1,478 @@ + + */ +class CreateGroupsGroupIdRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createGroupsGroupId_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'subject' => 'string', + 'description' => 'string', + 'profile_picture_file' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'subject' => null, + 'description' => null, + 'profile_picture_file' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'subject' => false, + 'description' => false, + 'profile_picture_file' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'subject' => 'subject', + 'description' => 'description', + 'profile_picture_file' => 'profile_picture_file' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'subject' => 'setSubject', + 'description' => 'setDescription', + 'profile_picture_file' => 'setProfilePictureFile' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'subject' => 'getSubject', + 'description' => 'getDescription', + 'profile_picture_file' => 'getProfilePictureFile' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('subject', $data ?? [], null); + $this->setIfExists('description', $data ?? [], null); + $this->setIfExists('profile_picture_file', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets subject + * + * @return string|null + */ + public function getSubject() + { + return $this->container['subject']; + } + + /** + * Sets subject + * + * @param string|null $subject New group subject / name + * + * @return self + */ + public function setSubject($subject) + { + if (is_null($subject)) { + throw new \InvalidArgumentException('non-nullable subject cannot be null'); + } + $this->container['subject'] = $subject; + + return $this; + } + + /** + * Gets description + * + * @return string|null + */ + public function getDescription() + { + return $this->container['description']; + } + + /** + * Sets description + * + * @param string|null $description New group description + * + * @return self + */ + public function setDescription($description) + { + if (is_null($description)) { + throw new \InvalidArgumentException('non-nullable description cannot be null'); + } + $this->container['description'] = $description; + + return $this; + } + + /** + * Gets profile_picture_file + * + * @return string|null + */ + public function getProfilePictureFile() + { + return $this->container['profile_picture_file']; + } + + /** + * Sets profile_picture_file + * + * @param string|null $profile_picture_file Image URL or base64 data URI for the group profile picture + * + * @return self + */ + public function setProfilePictureFile($profile_picture_file) + { + if (is_null($profile_picture_file)) { + throw new \InvalidArgumentException('non-nullable profile_picture_file cannot be null'); + } + $this->container['profile_picture_file'] = $profile_picture_file; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/CreateGroupsRequest.php b/lib/Model/CreateGroupsRequest.php new file mode 100644 index 0000000..737063f --- /dev/null +++ b/lib/Model/CreateGroupsRequest.php @@ -0,0 +1,447 @@ + + */ +class CreateGroupsRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createGroups_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'group_name' => 'string', + 'description' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'group_name' => null, + 'description' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'group_name' => false, + 'description' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'group_name' => 'groupName', + 'description' => 'description' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'group_name' => 'setGroupName', + 'description' => 'setDescription' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'group_name' => 'getGroupName', + 'description' => 'getDescription' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('group_name', $data ?? [], null); + $this->setIfExists('description', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['group_name'] === null) { + $invalidProperties[] = "'group_name' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets group_name + * + * @return string + */ + public function getGroupName() + { + return $this->container['group_name']; + } + + /** + * Sets group_name + * + * @param string $group_name Group subject / name + * + * @return self + */ + public function setGroupName($group_name) + { + if (is_null($group_name)) { + throw new \InvalidArgumentException('non-nullable group_name cannot be null'); + } + $this->container['group_name'] = $group_name; + + return $this; + } + + /** + * Gets description + * + * @return string|null + */ + public function getDescription() + { + return $this->container['description']; + } + + /** + * Sets description + * + * @param string|null $description Optional group description + * + * @return self + */ + public function setDescription($description) + { + if (is_null($description)) { + throw new \InvalidArgumentException('non-nullable description cannot be null'); + } + $this->container['description'] = $description; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/CreateReadMessage200Response.php b/lib/Model/CreateReadMessage200Response.php index f995220..ff9cef6 100644 --- a/lib/Model/CreateReadMessage200Response.php +++ b/lib/Model/CreateReadMessage200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateReadMessage401Response.php b/lib/Model/CreateReadMessage401Response.php index 3f7349a..0f390e7 100644 --- a/lib/Model/CreateReadMessage401Response.php +++ b/lib/Model/CreateReadMessage401Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateReadMessageRequest.php b/lib/Model/CreateReadMessageRequest.php index 60aba1b..ce6b196 100644 --- a/lib/Model/CreateReadMessageRequest.php +++ b/lib/Model/CreateReadMessageRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/CreateSettingsRequest.php b/lib/Model/CreateSettingsRequest.php new file mode 100644 index 0000000..dc6ad8e --- /dev/null +++ b/lib/Model/CreateSettingsRequest.php @@ -0,0 +1,580 @@ + + */ +class CreateSettingsRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createSettings_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'webhook_url' => '\OneMsgSdk\Model\CreateSettingsRequestWebhookUrl', + 'guaranteed_hooks' => 'bool', + 'ack_notifications_on' => 'bool', + 'raw_hooks' => 'bool', + 'template_analytics_enabled' => 'bool', + 'cta_url_link_tracking_opted_out' => 'bool' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'webhook_url' => null, + 'guaranteed_hooks' => null, + 'ack_notifications_on' => null, + 'raw_hooks' => null, + 'template_analytics_enabled' => null, + 'cta_url_link_tracking_opted_out' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'webhook_url' => false, + 'guaranteed_hooks' => false, + 'ack_notifications_on' => false, + 'raw_hooks' => false, + 'template_analytics_enabled' => false, + 'cta_url_link_tracking_opted_out' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'webhook_url' => 'webhookUrl', + 'guaranteed_hooks' => 'guaranteedHooks', + 'ack_notifications_on' => 'ackNotificationsOn', + 'raw_hooks' => 'rawHooks', + 'template_analytics_enabled' => 'template_analytics_enabled', + 'cta_url_link_tracking_opted_out' => 'cta_url_link_tracking_opted_out' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'webhook_url' => 'setWebhookUrl', + 'guaranteed_hooks' => 'setGuaranteedHooks', + 'ack_notifications_on' => 'setAckNotificationsOn', + 'raw_hooks' => 'setRawHooks', + 'template_analytics_enabled' => 'setTemplateAnalyticsEnabled', + 'cta_url_link_tracking_opted_out' => 'setCtaUrlLinkTrackingOptedOut' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'webhook_url' => 'getWebhookUrl', + 'guaranteed_hooks' => 'getGuaranteedHooks', + 'ack_notifications_on' => 'getAckNotificationsOn', + 'raw_hooks' => 'getRawHooks', + 'template_analytics_enabled' => 'getTemplateAnalyticsEnabled', + 'cta_url_link_tracking_opted_out' => 'getCtaUrlLinkTrackingOptedOut' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('webhook_url', $data ?? [], null); + $this->setIfExists('guaranteed_hooks', $data ?? [], null); + $this->setIfExists('ack_notifications_on', $data ?? [], null); + $this->setIfExists('raw_hooks', $data ?? [], null); + $this->setIfExists('template_analytics_enabled', $data ?? [], null); + $this->setIfExists('cta_url_link_tracking_opted_out', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets webhook_url + * + * @return \OneMsgSdk\Model\CreateSettingsRequestWebhookUrl|null + */ + public function getWebhookUrl() + { + return $this->container['webhook_url']; + } + + /** + * Sets webhook_url + * + * @param \OneMsgSdk\Model\CreateSettingsRequestWebhookUrl|null $webhook_url webhook_url + * + * @return self + */ + public function setWebhookUrl($webhook_url) + { + if (is_null($webhook_url)) { + throw new \InvalidArgumentException('non-nullable webhook_url cannot be null'); + } + $this->container['webhook_url'] = $webhook_url; + + return $this; + } + + /** + * Gets guaranteed_hooks + * + * @return bool|null + */ + public function getGuaranteedHooks() + { + return $this->container['guaranteed_hooks']; + } + + /** + * Sets guaranteed_hooks + * + * @param bool|null $guaranteed_hooks guaranteed_hooks + * + * @return self + */ + public function setGuaranteedHooks($guaranteed_hooks) + { + if (is_null($guaranteed_hooks)) { + throw new \InvalidArgumentException('non-nullable guaranteed_hooks cannot be null'); + } + $this->container['guaranteed_hooks'] = $guaranteed_hooks; + + return $this; + } + + /** + * Gets ack_notifications_on + * + * @return bool|null + */ + public function getAckNotificationsOn() + { + return $this->container['ack_notifications_on']; + } + + /** + * Sets ack_notifications_on + * + * @param bool|null $ack_notifications_on ack_notifications_on + * + * @return self + */ + public function setAckNotificationsOn($ack_notifications_on) + { + if (is_null($ack_notifications_on)) { + throw new \InvalidArgumentException('non-nullable ack_notifications_on cannot be null'); + } + $this->container['ack_notifications_on'] = $ack_notifications_on; + + return $this; + } + + /** + * Gets raw_hooks + * + * @return bool|null + */ + public function getRawHooks() + { + return $this->container['raw_hooks']; + } + + /** + * Sets raw_hooks + * + * @param bool|null $raw_hooks raw_hooks + * + * @return self + */ + public function setRawHooks($raw_hooks) + { + if (is_null($raw_hooks)) { + throw new \InvalidArgumentException('non-nullable raw_hooks cannot be null'); + } + $this->container['raw_hooks'] = $raw_hooks; + + return $this; + } + + /** + * Gets template_analytics_enabled + * + * @return bool|null + */ + public function getTemplateAnalyticsEnabled() + { + return $this->container['template_analytics_enabled']; + } + + /** + * Sets template_analytics_enabled + * + * @param bool|null $template_analytics_enabled template_analytics_enabled + * + * @return self + */ + public function setTemplateAnalyticsEnabled($template_analytics_enabled) + { + if (is_null($template_analytics_enabled)) { + throw new \InvalidArgumentException('non-nullable template_analytics_enabled cannot be null'); + } + $this->container['template_analytics_enabled'] = $template_analytics_enabled; + + return $this; + } + + /** + * Gets cta_url_link_tracking_opted_out + * + * @return bool|null + */ + public function getCtaUrlLinkTrackingOptedOut() + { + return $this->container['cta_url_link_tracking_opted_out']; + } + + /** + * Sets cta_url_link_tracking_opted_out + * + * @param bool|null $cta_url_link_tracking_opted_out cta_url_link_tracking_opted_out + * + * @return self + */ + public function setCtaUrlLinkTrackingOptedOut($cta_url_link_tracking_opted_out) + { + if (is_null($cta_url_link_tracking_opted_out)) { + throw new \InvalidArgumentException('non-nullable cta_url_link_tracking_opted_out cannot be null'); + } + $this->container['cta_url_link_tracking_opted_out'] = $cta_url_link_tracking_opted_out; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/CreateSettingsRequestWebhookUrl.php b/lib/Model/CreateSettingsRequestWebhookUrl.php new file mode 100644 index 0000000..c51582c --- /dev/null +++ b/lib/Model/CreateSettingsRequestWebhookUrl.php @@ -0,0 +1,383 @@ + + */ +class CreateSettingsRequestWebhookUrl implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createSettings_request_webhookUrl'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/CreateUploadMedia200Response.php b/lib/Model/CreateUploadMedia200Response.php new file mode 100644 index 0000000..e747fe6 --- /dev/null +++ b/lib/Model/CreateUploadMedia200Response.php @@ -0,0 +1,410 @@ + + */ +class CreateUploadMedia200Response implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createUploadMedia_200_response'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'media_id' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'media_id' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'media_id' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'media_id' => 'mediaId' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'media_id' => 'setMediaId' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'media_id' => 'getMediaId' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('media_id', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets media_id + * + * @return string|null + */ + public function getMediaId() + { + return $this->container['media_id']; + } + + /** + * Sets media_id + * + * @param string|null $media_id media_id + * + * @return self + */ + public function setMediaId($media_id) + { + if (is_null($media_id)) { + throw new \InvalidArgumentException('non-nullable media_id cannot be null'); + } + $this->container['media_id'] = $media_id; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/CreateUploadMediaRequest.php b/lib/Model/CreateUploadMediaRequest.php new file mode 100644 index 0000000..1af7340 --- /dev/null +++ b/lib/Model/CreateUploadMediaRequest.php @@ -0,0 +1,444 @@ + + */ +class CreateUploadMediaRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'createUploadMedia_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'body' => 'string', + 'url' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'body' => null, + 'url' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'body' => false, + 'url' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'body' => 'body', + 'url' => 'url' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'body' => 'setBody', + 'url' => 'setUrl' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'body' => 'getBody', + 'url' => 'getUrl' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('body', $data ?? [], null); + $this->setIfExists('url', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets body + * + * @return string|null + */ + public function getBody() + { + return $this->container['body']; + } + + /** + * Sets body + * + * @param string|null $body File HTTP(S) URL or base64 data URI (`data:;base64,...`) + * + * @return self + */ + public function setBody($body) + { + if (is_null($body)) { + throw new \InvalidArgumentException('non-nullable body cannot be null'); + } + $this->container['body'] = $body; + + return $this; + } + + /** + * Gets url + * + * @return string|null + */ + public function getUrl() + { + return $this->container['url']; + } + + /** + * Sets url + * + * @param string|null $url Alias for `body` — public HTTP(S) URL of the file to upload + * + * @return self + */ + public function setUrl($url) + { + if (is_null($url)) { + throw new \InvalidArgumentException('non-nullable url cannot be null'); + } + $this->container['url'] = $url; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/DeleteFlowsFlowId200Response.php b/lib/Model/DeleteFlowsFlowId200Response.php index 95663ac..3b3e144 100644 --- a/lib/Model/DeleteFlowsFlowId200Response.php +++ b/lib/Model/DeleteFlowsFlowId200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/DeleteGroupsGroupId200Response.php b/lib/Model/DeleteGroupsGroupId200Response.php index 65d219b..b5feb04 100644 --- a/lib/Model/DeleteGroupsGroupId200Response.php +++ b/lib/Model/DeleteGroupsGroupId200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/DeleteMediaLegacyRequest.php b/lib/Model/DeleteMediaLegacyRequest.php index 4fdd8f3..58e1573 100644 --- a/lib/Model/DeleteMediaLegacyRequest.php +++ b/lib/Model/DeleteMediaLegacyRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ErrorResponse.php b/lib/Model/ErrorResponse.php index 8e7f5c6..af9643f 100644 --- a/lib/Model/ErrorResponse.php +++ b/lib/Model/ErrorResponse.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/GetCommerce200ResponseInner.php b/lib/Model/GetCommerce200ResponseInner.php index 93c0319..65a1d61 100644 --- a/lib/Model/GetCommerce200ResponseInner.php +++ b/lib/Model/GetCommerce200ResponseInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/GetMmLiteStatus200Response.php b/lib/Model/GetMmLiteStatus200Response.php index 3c7aa49..407ccdd 100644 --- a/lib/Model/GetMmLiteStatus200Response.php +++ b/lib/Model/GetMmLiteStatus200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/GetStatus200Response.php b/lib/Model/GetStatus200Response.php new file mode 100644 index 0000000..8ac5745 --- /dev/null +++ b/lib/Model/GetStatus200Response.php @@ -0,0 +1,546 @@ + + */ +class GetStatus200Response implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'getStatus_200_response'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'is_cloud' => 'bool', + 'status' => 'string', + 'account_status' => 'string', + 'mm_lite_available' => 'bool', + 'health_status_warning' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'is_cloud' => null, + 'status' => null, + 'account_status' => null, + 'mm_lite_available' => null, + 'health_status_warning' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'is_cloud' => false, + 'status' => false, + 'account_status' => false, + 'mm_lite_available' => false, + 'health_status_warning' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'is_cloud' => 'isCloud', + 'status' => 'status', + 'account_status' => 'accountStatus', + 'mm_lite_available' => 'mm_lite_available', + 'health_status_warning' => 'health_status_warning' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'is_cloud' => 'setIsCloud', + 'status' => 'setStatus', + 'account_status' => 'setAccountStatus', + 'mm_lite_available' => 'setMmLiteAvailable', + 'health_status_warning' => 'setHealthStatusWarning' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'is_cloud' => 'getIsCloud', + 'status' => 'getStatus', + 'account_status' => 'getAccountStatus', + 'mm_lite_available' => 'getMmLiteAvailable', + 'health_status_warning' => 'getHealthStatusWarning' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('is_cloud', $data ?? [], null); + $this->setIfExists('status', $data ?? [], null); + $this->setIfExists('account_status', $data ?? [], null); + $this->setIfExists('mm_lite_available', $data ?? [], null); + $this->setIfExists('health_status_warning', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets is_cloud + * + * @return bool|null + */ + public function getIsCloud() + { + return $this->container['is_cloud']; + } + + /** + * Sets is_cloud + * + * @param bool|null $is_cloud is_cloud + * + * @return self + */ + public function setIsCloud($is_cloud) + { + if (is_null($is_cloud)) { + throw new \InvalidArgumentException('non-nullable is_cloud cannot be null'); + } + $this->container['is_cloud'] = $is_cloud; + + return $this; + } + + /** + * Gets status + * + * @return string|null + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param string|null $status typically `connected` when the number can send + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + throw new \InvalidArgumentException('non-nullable status cannot be null'); + } + $this->container['status'] = $status; + + return $this; + } + + /** + * Gets account_status + * + * @return string|null + */ + public function getAccountStatus() + { + return $this->container['account_status']; + } + + /** + * Sets account_status + * + * @param string|null $account_status account_status + * + * @return self + */ + public function setAccountStatus($account_status) + { + if (is_null($account_status)) { + throw new \InvalidArgumentException('non-nullable account_status cannot be null'); + } + $this->container['account_status'] = $account_status; + + return $this; + } + + /** + * Gets mm_lite_available + * + * @return bool|null + */ + public function getMmLiteAvailable() + { + return $this->container['mm_lite_available']; + } + + /** + * Sets mm_lite_available + * + * @param bool|null $mm_lite_available mm_lite_available + * + * @return self + */ + public function setMmLiteAvailable($mm_lite_available) + { + if (is_null($mm_lite_available)) { + throw new \InvalidArgumentException('non-nullable mm_lite_available cannot be null'); + } + $this->container['mm_lite_available'] = $mm_lite_available; + + return $this; + } + + /** + * Gets health_status_warning + * + * @return string|null + */ + public function getHealthStatusWarning() + { + return $this->container['health_status_warning']; + } + + /** + * Sets health_status_warning + * + * @param string|null $health_status_warning Optional soft-fail warning when Meta Graph health_status could not be loaded. Never includes internal provider names. + * + * @return self + */ + public function setHealthStatusWarning($health_status_warning) + { + if (is_null($health_status_warning)) { + throw new \InvalidArgumentException('non-nullable health_status_warning cannot be null'); + } + $this->container['health_status_warning'] = $health_status_warning; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/GetWebhook200Response.php b/lib/Model/GetWebhook200Response.php index 6f8e174..be43b16 100644 --- a/lib/Model/GetWebhook200Response.php +++ b/lib/Model/GetWebhook200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -58,7 +58,18 @@ class GetWebhook200Response implements ModelInterface, ArrayAccess, \JsonSeriali * @var string[] */ protected static $openAPITypes = [ - 'webhook_url' => 'string' + 'webhook_url' => '\OneMsgSdk\Model\GetWebhook200ResponseWebhookUrl', + 'have_disabled_webhooks' => 'bool', + 'circuit_state' => 'string', + 'reason' => 'string', + 'fails' => 'int', + 'trips' => 'int', + 'disabled_at' => 'int', + 'first_fail_at' => 'int', + 'last_status' => 'int', + 'next_probe_at' => 'int', + 'disabled_webhooks' => 'array[]', + 'webhook_statuses' => '\OneMsgSdk\Model\WebhookUrlStatus[]' ]; /** @@ -69,7 +80,18 @@ class GetWebhook200Response implements ModelInterface, ArrayAccess, \JsonSeriali * @psalm-var array */ protected static $openAPIFormats = [ - 'webhook_url' => null + 'webhook_url' => null, + 'have_disabled_webhooks' => null, + 'circuit_state' => null, + 'reason' => null, + 'fails' => null, + 'trips' => null, + 'disabled_at' => null, + 'first_fail_at' => null, + 'last_status' => null, + 'next_probe_at' => null, + 'disabled_webhooks' => null, + 'webhook_statuses' => null ]; /** @@ -78,7 +100,18 @@ class GetWebhook200Response implements ModelInterface, ArrayAccess, \JsonSeriali * @var boolean[] */ protected static array $openAPINullables = [ - 'webhook_url' => false + 'webhook_url' => true, + 'have_disabled_webhooks' => false, + 'circuit_state' => false, + 'reason' => false, + 'fails' => false, + 'trips' => false, + 'disabled_at' => false, + 'first_fail_at' => false, + 'last_status' => false, + 'next_probe_at' => false, + 'disabled_webhooks' => false, + 'webhook_statuses' => false ]; /** @@ -167,7 +200,18 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $attributeMap = [ - 'webhook_url' => 'webhookUrl' + 'webhook_url' => 'webhookUrl', + 'have_disabled_webhooks' => 'haveDisabledWebhooks', + 'circuit_state' => 'circuitState', + 'reason' => 'reason', + 'fails' => 'fails', + 'trips' => 'trips', + 'disabled_at' => 'disabledAt', + 'first_fail_at' => 'firstFailAt', + 'last_status' => 'lastStatus', + 'next_probe_at' => 'nextProbeAt', + 'disabled_webhooks' => 'disabledWebhooks', + 'webhook_statuses' => 'webhookStatuses' ]; /** @@ -176,7 +220,18 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $setters = [ - 'webhook_url' => 'setWebhookUrl' + 'webhook_url' => 'setWebhookUrl', + 'have_disabled_webhooks' => 'setHaveDisabledWebhooks', + 'circuit_state' => 'setCircuitState', + 'reason' => 'setReason', + 'fails' => 'setFails', + 'trips' => 'setTrips', + 'disabled_at' => 'setDisabledAt', + 'first_fail_at' => 'setFirstFailAt', + 'last_status' => 'setLastStatus', + 'next_probe_at' => 'setNextProbeAt', + 'disabled_webhooks' => 'setDisabledWebhooks', + 'webhook_statuses' => 'setWebhookStatuses' ]; /** @@ -185,7 +240,18 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $getters = [ - 'webhook_url' => 'getWebhookUrl' + 'webhook_url' => 'getWebhookUrl', + 'have_disabled_webhooks' => 'getHaveDisabledWebhooks', + 'circuit_state' => 'getCircuitState', + 'reason' => 'getReason', + 'fails' => 'getFails', + 'trips' => 'getTrips', + 'disabled_at' => 'getDisabledAt', + 'first_fail_at' => 'getFirstFailAt', + 'last_status' => 'getLastStatus', + 'next_probe_at' => 'getNextProbeAt', + 'disabled_webhooks' => 'getDisabledWebhooks', + 'webhook_statuses' => 'getWebhookStatuses' ]; /** @@ -229,6 +295,27 @@ public function getModelName() return self::$openAPIModelName; } + public const CIRCUIT_STATE_OK = 'ok'; + public const CIRCUIT_STATE_DEGRADED = 'degraded'; + public const CIRCUIT_STATE_OPEN = 'open'; + public const CIRCUIT_STATE_HALF_OPEN = 'half_open'; + public const CIRCUIT_STATE_DISABLED = 'disabled'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getCircuitStateAllowableValues() + { + return [ + self::CIRCUIT_STATE_OK, + self::CIRCUIT_STATE_DEGRADED, + self::CIRCUIT_STATE_OPEN, + self::CIRCUIT_STATE_HALF_OPEN, + self::CIRCUIT_STATE_DISABLED, + ]; + } /** * Associative array for storing property values @@ -246,6 +333,17 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('webhook_url', $data ?? [], null); + $this->setIfExists('have_disabled_webhooks', $data ?? [], null); + $this->setIfExists('circuit_state', $data ?? [], null); + $this->setIfExists('reason', $data ?? [], null); + $this->setIfExists('fails', $data ?? [], null); + $this->setIfExists('trips', $data ?? [], null); + $this->setIfExists('disabled_at', $data ?? [], null); + $this->setIfExists('first_fail_at', $data ?? [], null); + $this->setIfExists('last_status', $data ?? [], null); + $this->setIfExists('next_probe_at', $data ?? [], null); + $this->setIfExists('disabled_webhooks', $data ?? [], null); + $this->setIfExists('webhook_statuses', $data ?? [], null); } /** @@ -275,6 +373,15 @@ public function listInvalidProperties() { $invalidProperties = []; + $allowedValues = $this->getCircuitStateAllowableValues(); + if (!is_null($this->container['circuit_state']) && !in_array($this->container['circuit_state'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'circuit_state', must be one of '%s'", + $this->container['circuit_state'], + implode("', '", $allowedValues) + ); + } + return $invalidProperties; } @@ -293,7 +400,7 @@ public function valid() /** * Gets webhook_url * - * @return string|null + * @return \OneMsgSdk\Model\GetWebhook200ResponseWebhookUrl|null */ public function getWebhookUrl() { @@ -303,19 +410,333 @@ public function getWebhookUrl() /** * Sets webhook_url * - * @param string|null $webhook_url webhook_url + * @param \OneMsgSdk\Model\GetWebhook200ResponseWebhookUrl|null $webhook_url webhook_url * * @return self */ public function setWebhookUrl($webhook_url) { if (is_null($webhook_url)) { - throw new \InvalidArgumentException('non-nullable webhook_url cannot be null'); + array_push($this->openAPINullablesSetToNull, 'webhook_url'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('webhook_url', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } } $this->container['webhook_url'] = $webhook_url; return $this; } + + /** + * Gets have_disabled_webhooks + * + * @return bool|null + */ + public function getHaveDisabledWebhooks() + { + return $this->container['have_disabled_webhooks']; + } + + /** + * Sets have_disabled_webhooks + * + * @param bool|null $have_disabled_webhooks `true` when at least one URL in `webhookUrl` is sticky auto-disabled. Not \"the channel is dead\": other configured URLs still receive events. Send/receive are unaffected. + * + * @return self + */ + public function setHaveDisabledWebhooks($have_disabled_webhooks) + { + if (is_null($have_disabled_webhooks)) { + throw new \InvalidArgumentException('non-nullable have_disabled_webhooks cannot be null'); + } + $this->container['have_disabled_webhooks'] = $have_disabled_webhooks; + + return $this; + } + + /** + * Gets circuit_state + * + * @return string|null + */ + public function getCircuitState() + { + return $this->container['circuit_state']; + } + + /** + * Sets circuit_state + * + * @param string|null $circuit_state Compatibility scalar for **one** URL (first sticky-disabled, else first configured). Not the channel as a whole. Prefer `webhookStatuses[].circuitState`. + * + * @return self + */ + public function setCircuitState($circuit_state) + { + if (is_null($circuit_state)) { + throw new \InvalidArgumentException('non-nullable circuit_state cannot be null'); + } + $allowedValues = $this->getCircuitStateAllowableValues(); + if (!in_array($circuit_state, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'circuit_state', must be one of '%s'", + $circuit_state, + implode("', '", $allowedValues) + ) + ); + } + $this->container['circuit_state'] = $circuit_state; + + return $this; + } + + /** + * Gets reason + * + * @return string|null + */ + public function getReason() + { + return $this->container['reason']; + } + + /** + * Sets reason + * + * @param string|null $reason Last qualifying fail reason of the summary URL (same URL as `circuitState`) + * + * @return self + */ + public function setReason($reason) + { + if (is_null($reason)) { + throw new \InvalidArgumentException('non-nullable reason cannot be null'); + } + $this->container['reason'] = $reason; + + return $this; + } + + /** + * Gets fails + * + * @return int|null + */ + public function getFails() + { + return $this->container['fails']; + } + + /** + * Sets fails + * + * @param int|null $fails Diagnostic only (summary URL). Qualifying failures in the rolling ~10 minute breaker window — not lifetime and not customer retries. Prefer `webhookStatuses` / `disabledWebhooks`. + * + * @return self + */ + public function setFails($fails) + { + if (is_null($fails)) { + throw new \InvalidArgumentException('non-nullable fails cannot be null'); + } + $this->container['fails'] = $fails; + + return $this; + } + + /** + * Gets trips + * + * @return int|null + */ + public function getTrips() + { + return $this->container['trips']; + } + + /** + * Sets trips + * + * @param int|null $trips Diagnostic only (summary URL). Failed recovery probes (≈15 min → 1 h → 6 h). Sticky disable after 3. Not a blind unblock timer. + * + * @return self + */ + public function setTrips($trips) + { + if (is_null($trips)) { + throw new \InvalidArgumentException('non-nullable trips cannot be null'); + } + $this->container['trips'] = $trips; + + return $this; + } + + /** + * Gets disabled_at + * + * @return int|null + */ + public function getDisabledAt() + { + return $this->container['disabled_at']; + } + + /** + * Sets disabled_at + * + * @param int|null $disabled_at Epoch milliseconds when the summary URL became sticky-disabled + * + * @return self + */ + public function setDisabledAt($disabled_at) + { + if (is_null($disabled_at)) { + throw new \InvalidArgumentException('non-nullable disabled_at cannot be null'); + } + $this->container['disabled_at'] = $disabled_at; + + return $this; + } + + /** + * Gets first_fail_at + * + * @return int|null + */ + public function getFirstFailAt() + { + return $this->container['first_fail_at']; + } + + /** + * Sets first_fail_at + * + * @param int|null $first_fail_at Diagnostic only (summary URL). Epoch ms of the first fail in the current ~10 minute window (pairs with `fails`). + * + * @return self + */ + public function setFirstFailAt($first_fail_at) + { + if (is_null($first_fail_at)) { + throw new \InvalidArgumentException('non-nullable first_fail_at cannot be null'); + } + $this->container['first_fail_at'] = $first_fail_at; + + return $this; + } + + /** + * Gets last_status + * + * @return int|null + */ + public function getLastStatus() + { + return $this->container['last_status']; + } + + /** + * Sets last_status + * + * @param int|null $last_status Last HTTP status from the summary URL, when known + * + * @return self + */ + public function setLastStatus($last_status) + { + if (is_null($last_status)) { + throw new \InvalidArgumentException('non-nullable last_status cannot be null'); + } + $this->container['last_status'] = $last_status; + + return $this; + } + + /** + * Gets next_probe_at + * + * @return int|null + */ + public function getNextProbeAt() + { + return $this->container['next_probe_at']; + } + + /** + * Sets next_probe_at + * + * @param int|null $next_probe_at Epoch milliseconds of the next half-open probe for the summary URL, if any + * + * @return self + */ + public function setNextProbeAt($next_probe_at) + { + if (is_null($next_probe_at)) { + throw new \InvalidArgumentException('non-nullable next_probe_at cannot be null'); + } + $this->container['next_probe_at'] = $next_probe_at; + + return $this; + } + + /** + * Gets disabled_webhooks + * + * @return array[]|null + */ + public function getDisabledWebhooks() + { + return $this->container['disabled_webhooks']; + } + + /** + * Sets disabled_webhooks + * + * @param array[]|null $disabled_webhooks Currently configured URLs that are sticky auto-disabled (subset of `webhookUrl`). Does not include leftover URLs that are no longer configured. + * + * @return self + */ + public function setDisabledWebhooks($disabled_webhooks) + { + if (is_null($disabled_webhooks)) { + throw new \InvalidArgumentException('non-nullable disabled_webhooks cannot be null'); + } + $this->container['disabled_webhooks'] = $disabled_webhooks; + + return $this; + } + + /** + * Gets webhook_statuses + * + * @return \OneMsgSdk\Model\WebhookUrlStatus[]|null + */ + public function getWebhookStatuses() + { + return $this->container['webhook_statuses']; + } + + /** + * Sets webhook_statuses + * + * @param \OneMsgSdk\Model\WebhookUrlStatus[]|null $webhook_statuses One entry per currently configured URL. Prefer this over top-level `haveDisabledWebhooks` / `circuitState` when more than one webhook is set. + * + * @return self + */ + public function setWebhookStatuses($webhook_statuses) + { + if (is_null($webhook_statuses)) { + throw new \InvalidArgumentException('non-nullable webhook_statuses cannot be null'); + } + $this->container['webhook_statuses'] = $webhook_statuses; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/lib/Model/GetWebhook200ResponseWebhookUrl.php b/lib/Model/GetWebhook200ResponseWebhookUrl.php new file mode 100644 index 0000000..68a54e2 --- /dev/null +++ b/lib/Model/GetWebhook200ResponseWebhookUrl.php @@ -0,0 +1,383 @@ + + */ +class GetWebhook200ResponseWebhookUrl implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'getWebhook_200_response_webhookUrl'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/GetWhatsappBusinessEncryption200Response.php b/lib/Model/GetWhatsappBusinessEncryption200Response.php index 8ef2f6d..4b9e052 100644 --- a/lib/Model/GetWhatsappBusinessEncryption200Response.php +++ b/lib/Model/GetWhatsappBusinessEncryption200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/InitiateCallRequest.php b/lib/Model/InitiateCallRequest.php index ee055c2..cafa13b 100644 --- a/lib/Model/InitiateCallRequest.php +++ b/lib/Model/InitiateCallRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/InitiateCallRequestSession.php b/lib/Model/InitiateCallRequestSession.php index 8212d07..4d29888 100644 --- a/lib/Model/InitiateCallRequestSession.php +++ b/lib/Model/InitiateCallRequestSession.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/InitiateCallResponse.php b/lib/Model/InitiateCallResponse.php index 817342b..1f9e7f8 100644 --- a/lib/Model/InitiateCallResponse.php +++ b/lib/Model/InitiateCallResponse.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/InitiateCallResponseCallsInner.php b/lib/Model/InitiateCallResponseCallsInner.php index 21dd834..bdc11fb 100644 --- a/lib/Model/InitiateCallResponseCallsInner.php +++ b/lib/Model/InitiateCallResponseCallsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/InitiateCallResponseResponse.php b/lib/Model/InitiateCallResponseResponse.php index b46084e..f595c8a 100644 --- a/lib/Model/InitiateCallResponseResponse.php +++ b/lib/Model/InitiateCallResponseResponse.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ListBlockedUsers200Response.php b/lib/Model/ListBlockedUsers200Response.php index 3f1bf45..97938f6 100644 --- a/lib/Model/ListBlockedUsers200Response.php +++ b/lib/Model/ListBlockedUsers200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ListFlows200Response.php b/lib/Model/ListFlows200Response.php index c774247..3c47f7a 100644 --- a/lib/Model/ListFlows200Response.php +++ b/lib/Model/ListFlows200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ListMessages200Response.php b/lib/Model/ListMessages200Response.php index 4fde936..047d0d8 100644 --- a/lib/Model/ListMessages200Response.php +++ b/lib/Model/ListMessages200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ListSettings200Response.php b/lib/Model/ListSettings200Response.php new file mode 100644 index 0000000..eb48965 --- /dev/null +++ b/lib/Model/ListSettings200Response.php @@ -0,0 +1,1028 @@ + + */ +class ListSettings200Response implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'listSettings_200_response'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'webhook_url' => '\OneMsgSdk\Model\ListSettings200ResponseWebhookUrl', + 'guaranteed_hooks' => 'bool', + 'ack_notifications_on' => 'bool', + 'raw_hooks' => 'bool', + 'mm_lite_enabled' => 'bool', + 'template_analytics_enabled' => 'bool', + 'cta_url_link_tracking_opted_out' => 'bool', + 'have_disabled_webhooks' => 'bool', + 'circuit_state' => 'string', + 'reason' => 'string', + 'fails' => 'int', + 'trips' => 'int', + 'disabled_at' => 'int', + 'first_fail_at' => 'int', + 'last_status' => 'int', + 'next_probe_at' => 'int', + 'disabled_webhooks' => 'array[]', + 'webhook_statuses' => '\OneMsgSdk\Model\WebhookUrlStatus[]' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'webhook_url' => null, + 'guaranteed_hooks' => null, + 'ack_notifications_on' => null, + 'raw_hooks' => null, + 'mm_lite_enabled' => null, + 'template_analytics_enabled' => null, + 'cta_url_link_tracking_opted_out' => null, + 'have_disabled_webhooks' => null, + 'circuit_state' => null, + 'reason' => null, + 'fails' => null, + 'trips' => null, + 'disabled_at' => null, + 'first_fail_at' => null, + 'last_status' => null, + 'next_probe_at' => null, + 'disabled_webhooks' => null, + 'webhook_statuses' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'webhook_url' => false, + 'guaranteed_hooks' => false, + 'ack_notifications_on' => false, + 'raw_hooks' => false, + 'mm_lite_enabled' => false, + 'template_analytics_enabled' => false, + 'cta_url_link_tracking_opted_out' => false, + 'have_disabled_webhooks' => false, + 'circuit_state' => false, + 'reason' => false, + 'fails' => false, + 'trips' => false, + 'disabled_at' => false, + 'first_fail_at' => false, + 'last_status' => false, + 'next_probe_at' => false, + 'disabled_webhooks' => false, + 'webhook_statuses' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'webhook_url' => 'webhookUrl', + 'guaranteed_hooks' => 'guaranteedHooks', + 'ack_notifications_on' => 'ackNotificationsOn', + 'raw_hooks' => 'rawHooks', + 'mm_lite_enabled' => 'mm_lite_enabled', + 'template_analytics_enabled' => 'template_analytics_enabled', + 'cta_url_link_tracking_opted_out' => 'cta_url_link_tracking_opted_out', + 'have_disabled_webhooks' => 'haveDisabledWebhooks', + 'circuit_state' => 'circuitState', + 'reason' => 'reason', + 'fails' => 'fails', + 'trips' => 'trips', + 'disabled_at' => 'disabledAt', + 'first_fail_at' => 'firstFailAt', + 'last_status' => 'lastStatus', + 'next_probe_at' => 'nextProbeAt', + 'disabled_webhooks' => 'disabledWebhooks', + 'webhook_statuses' => 'webhookStatuses' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'webhook_url' => 'setWebhookUrl', + 'guaranteed_hooks' => 'setGuaranteedHooks', + 'ack_notifications_on' => 'setAckNotificationsOn', + 'raw_hooks' => 'setRawHooks', + 'mm_lite_enabled' => 'setMmLiteEnabled', + 'template_analytics_enabled' => 'setTemplateAnalyticsEnabled', + 'cta_url_link_tracking_opted_out' => 'setCtaUrlLinkTrackingOptedOut', + 'have_disabled_webhooks' => 'setHaveDisabledWebhooks', + 'circuit_state' => 'setCircuitState', + 'reason' => 'setReason', + 'fails' => 'setFails', + 'trips' => 'setTrips', + 'disabled_at' => 'setDisabledAt', + 'first_fail_at' => 'setFirstFailAt', + 'last_status' => 'setLastStatus', + 'next_probe_at' => 'setNextProbeAt', + 'disabled_webhooks' => 'setDisabledWebhooks', + 'webhook_statuses' => 'setWebhookStatuses' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'webhook_url' => 'getWebhookUrl', + 'guaranteed_hooks' => 'getGuaranteedHooks', + 'ack_notifications_on' => 'getAckNotificationsOn', + 'raw_hooks' => 'getRawHooks', + 'mm_lite_enabled' => 'getMmLiteEnabled', + 'template_analytics_enabled' => 'getTemplateAnalyticsEnabled', + 'cta_url_link_tracking_opted_out' => 'getCtaUrlLinkTrackingOptedOut', + 'have_disabled_webhooks' => 'getHaveDisabledWebhooks', + 'circuit_state' => 'getCircuitState', + 'reason' => 'getReason', + 'fails' => 'getFails', + 'trips' => 'getTrips', + 'disabled_at' => 'getDisabledAt', + 'first_fail_at' => 'getFirstFailAt', + 'last_status' => 'getLastStatus', + 'next_probe_at' => 'getNextProbeAt', + 'disabled_webhooks' => 'getDisabledWebhooks', + 'webhook_statuses' => 'getWebhookStatuses' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const CIRCUIT_STATE_OK = 'ok'; + public const CIRCUIT_STATE_DEGRADED = 'degraded'; + public const CIRCUIT_STATE_OPEN = 'open'; + public const CIRCUIT_STATE_HALF_OPEN = 'half_open'; + public const CIRCUIT_STATE_DISABLED = 'disabled'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getCircuitStateAllowableValues() + { + return [ + self::CIRCUIT_STATE_OK, + self::CIRCUIT_STATE_DEGRADED, + self::CIRCUIT_STATE_OPEN, + self::CIRCUIT_STATE_HALF_OPEN, + self::CIRCUIT_STATE_DISABLED, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('webhook_url', $data ?? [], null); + $this->setIfExists('guaranteed_hooks', $data ?? [], null); + $this->setIfExists('ack_notifications_on', $data ?? [], null); + $this->setIfExists('raw_hooks', $data ?? [], null); + $this->setIfExists('mm_lite_enabled', $data ?? [], null); + $this->setIfExists('template_analytics_enabled', $data ?? [], null); + $this->setIfExists('cta_url_link_tracking_opted_out', $data ?? [], null); + $this->setIfExists('have_disabled_webhooks', $data ?? [], null); + $this->setIfExists('circuit_state', $data ?? [], null); + $this->setIfExists('reason', $data ?? [], null); + $this->setIfExists('fails', $data ?? [], null); + $this->setIfExists('trips', $data ?? [], null); + $this->setIfExists('disabled_at', $data ?? [], null); + $this->setIfExists('first_fail_at', $data ?? [], null); + $this->setIfExists('last_status', $data ?? [], null); + $this->setIfExists('next_probe_at', $data ?? [], null); + $this->setIfExists('disabled_webhooks', $data ?? [], null); + $this->setIfExists('webhook_statuses', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + $allowedValues = $this->getCircuitStateAllowableValues(); + if (!is_null($this->container['circuit_state']) && !in_array($this->container['circuit_state'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'circuit_state', must be one of '%s'", + $this->container['circuit_state'], + implode("', '", $allowedValues) + ); + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets webhook_url + * + * @return \OneMsgSdk\Model\ListSettings200ResponseWebhookUrl|null + */ + public function getWebhookUrl() + { + return $this->container['webhook_url']; + } + + /** + * Sets webhook_url + * + * @param \OneMsgSdk\Model\ListSettings200ResponseWebhookUrl|null $webhook_url webhook_url + * + * @return self + */ + public function setWebhookUrl($webhook_url) + { + if (is_null($webhook_url)) { + throw new \InvalidArgumentException('non-nullable webhook_url cannot be null'); + } + $this->container['webhook_url'] = $webhook_url; + + return $this; + } + + /** + * Gets guaranteed_hooks + * + * @return bool|null + */ + public function getGuaranteedHooks() + { + return $this->container['guaranteed_hooks']; + } + + /** + * Sets guaranteed_hooks + * + * @param bool|null $guaranteed_hooks Retry webhooks until acknowledged + * + * @return self + */ + public function setGuaranteedHooks($guaranteed_hooks) + { + if (is_null($guaranteed_hooks)) { + throw new \InvalidArgumentException('non-nullable guaranteed_hooks cannot be null'); + } + $this->container['guaranteed_hooks'] = $guaranteed_hooks; + + return $this; + } + + /** + * Gets ack_notifications_on + * + * @return bool|null + */ + public function getAckNotificationsOn() + { + return $this->container['ack_notifications_on']; + } + + /** + * Sets ack_notifications_on + * + * @param bool|null $ack_notifications_on Include message ACK / status events in webhooks + * + * @return self + */ + public function setAckNotificationsOn($ack_notifications_on) + { + if (is_null($ack_notifications_on)) { + throw new \InvalidArgumentException('non-nullable ack_notifications_on cannot be null'); + } + $this->container['ack_notifications_on'] = $ack_notifications_on; + + return $this; + } + + /** + * Gets raw_hooks + * + * @return bool|null + */ + public function getRawHooks() + { + return $this->container['raw_hooks']; + } + + /** + * Sets raw_hooks + * + * @param bool|null $raw_hooks Forward raw provider webhook payloads + * + * @return self + */ + public function setRawHooks($raw_hooks) + { + if (is_null($raw_hooks)) { + throw new \InvalidArgumentException('non-nullable raw_hooks cannot be null'); + } + $this->container['raw_hooks'] = $raw_hooks; + + return $this; + } + + /** + * Gets mm_lite_enabled + * + * @return bool|null + */ + public function getMmLiteEnabled() + { + return $this->container['mm_lite_enabled']; + } + + /** + * Sets mm_lite_enabled + * + * @param bool|null $mm_lite_enabled mm_lite_enabled + * + * @return self + */ + public function setMmLiteEnabled($mm_lite_enabled) + { + if (is_null($mm_lite_enabled)) { + throw new \InvalidArgumentException('non-nullable mm_lite_enabled cannot be null'); + } + $this->container['mm_lite_enabled'] = $mm_lite_enabled; + + return $this; + } + + /** + * Gets template_analytics_enabled + * + * @return bool|null + */ + public function getTemplateAnalyticsEnabled() + { + return $this->container['template_analytics_enabled']; + } + + /** + * Sets template_analytics_enabled + * + * @param bool|null $template_analytics_enabled template_analytics_enabled + * + * @return self + */ + public function setTemplateAnalyticsEnabled($template_analytics_enabled) + { + if (is_null($template_analytics_enabled)) { + throw new \InvalidArgumentException('non-nullable template_analytics_enabled cannot be null'); + } + $this->container['template_analytics_enabled'] = $template_analytics_enabled; + + return $this; + } + + /** + * Gets cta_url_link_tracking_opted_out + * + * @return bool|null + */ + public function getCtaUrlLinkTrackingOptedOut() + { + return $this->container['cta_url_link_tracking_opted_out']; + } + + /** + * Sets cta_url_link_tracking_opted_out + * + * @param bool|null $cta_url_link_tracking_opted_out cta_url_link_tracking_opted_out + * + * @return self + */ + public function setCtaUrlLinkTrackingOptedOut($cta_url_link_tracking_opted_out) + { + if (is_null($cta_url_link_tracking_opted_out)) { + throw new \InvalidArgumentException('non-nullable cta_url_link_tracking_opted_out cannot be null'); + } + $this->container['cta_url_link_tracking_opted_out'] = $cta_url_link_tracking_opted_out; + + return $this; + } + + /** + * Gets have_disabled_webhooks + * + * @return bool|null + */ + public function getHaveDisabledWebhooks() + { + return $this->container['have_disabled_webhooks']; + } + + /** + * Sets have_disabled_webhooks + * + * @param bool|null $have_disabled_webhooks `true` when at least one URL in `webhookUrl` is sticky auto-disabled. Not \"the channel is dead\": other configured URLs still receive events. + * + * @return self + */ + public function setHaveDisabledWebhooks($have_disabled_webhooks) + { + if (is_null($have_disabled_webhooks)) { + throw new \InvalidArgumentException('non-nullable have_disabled_webhooks cannot be null'); + } + $this->container['have_disabled_webhooks'] = $have_disabled_webhooks; + + return $this; + } + + /** + * Gets circuit_state + * + * @return string|null + */ + public function getCircuitState() + { + return $this->container['circuit_state']; + } + + /** + * Sets circuit_state + * + * @param string|null $circuit_state Compatibility scalar for **one** URL (first sticky-disabled, else first configured). Prefer `webhookStatuses[].circuitState`. + * + * @return self + */ + public function setCircuitState($circuit_state) + { + if (is_null($circuit_state)) { + throw new \InvalidArgumentException('non-nullable circuit_state cannot be null'); + } + $allowedValues = $this->getCircuitStateAllowableValues(); + if (!in_array($circuit_state, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'circuit_state', must be one of '%s'", + $circuit_state, + implode("', '", $allowedValues) + ) + ); + } + $this->container['circuit_state'] = $circuit_state; + + return $this; + } + + /** + * Gets reason + * + * @return string|null + */ + public function getReason() + { + return $this->container['reason']; + } + + /** + * Sets reason + * + * @param string|null $reason Last qualifying fail reason of the summary URL + * + * @return self + */ + public function setReason($reason) + { + if (is_null($reason)) { + throw new \InvalidArgumentException('non-nullable reason cannot be null'); + } + $this->container['reason'] = $reason; + + return $this; + } + + /** + * Gets fails + * + * @return int|null + */ + public function getFails() + { + return $this->container['fails']; + } + + /** + * Sets fails + * + * @param int|null $fails Diagnostic only (summary URL). Qualifying failures in the rolling ~10 minute breaker window — not lifetime. Prefer `webhookStatuses`. + * + * @return self + */ + public function setFails($fails) + { + if (is_null($fails)) { + throw new \InvalidArgumentException('non-nullable fails cannot be null'); + } + $this->container['fails'] = $fails; + + return $this; + } + + /** + * Gets trips + * + * @return int|null + */ + public function getTrips() + { + return $this->container['trips']; + } + + /** + * Sets trips + * + * @param int|null $trips Diagnostic only (summary URL). Failed recovery probes (≈15 min → 1 h → 6 h). Sticky disable after 3. Not a blind unblock timer. + * + * @return self + */ + public function setTrips($trips) + { + if (is_null($trips)) { + throw new \InvalidArgumentException('non-nullable trips cannot be null'); + } + $this->container['trips'] = $trips; + + return $this; + } + + /** + * Gets disabled_at + * + * @return int|null + */ + public function getDisabledAt() + { + return $this->container['disabled_at']; + } + + /** + * Sets disabled_at + * + * @param int|null $disabled_at Epoch milliseconds when the summary URL became sticky-disabled + * + * @return self + */ + public function setDisabledAt($disabled_at) + { + if (is_null($disabled_at)) { + throw new \InvalidArgumentException('non-nullable disabled_at cannot be null'); + } + $this->container['disabled_at'] = $disabled_at; + + return $this; + } + + /** + * Gets first_fail_at + * + * @return int|null + */ + public function getFirstFailAt() + { + return $this->container['first_fail_at']; + } + + /** + * Sets first_fail_at + * + * @param int|null $first_fail_at Diagnostic only (summary URL). Epoch ms of the first fail in the current ~10 minute window (pairs with `fails`). + * + * @return self + */ + public function setFirstFailAt($first_fail_at) + { + if (is_null($first_fail_at)) { + throw new \InvalidArgumentException('non-nullable first_fail_at cannot be null'); + } + $this->container['first_fail_at'] = $first_fail_at; + + return $this; + } + + /** + * Gets last_status + * + * @return int|null + */ + public function getLastStatus() + { + return $this->container['last_status']; + } + + /** + * Sets last_status + * + * @param int|null $last_status Last HTTP status from the summary URL, when known + * + * @return self + */ + public function setLastStatus($last_status) + { + if (is_null($last_status)) { + throw new \InvalidArgumentException('non-nullable last_status cannot be null'); + } + $this->container['last_status'] = $last_status; + + return $this; + } + + /** + * Gets next_probe_at + * + * @return int|null + */ + public function getNextProbeAt() + { + return $this->container['next_probe_at']; + } + + /** + * Sets next_probe_at + * + * @param int|null $next_probe_at Epoch milliseconds of the next half-open probe for the summary URL, if any + * + * @return self + */ + public function setNextProbeAt($next_probe_at) + { + if (is_null($next_probe_at)) { + throw new \InvalidArgumentException('non-nullable next_probe_at cannot be null'); + } + $this->container['next_probe_at'] = $next_probe_at; + + return $this; + } + + /** + * Gets disabled_webhooks + * + * @return array[]|null + */ + public function getDisabledWebhooks() + { + return $this->container['disabled_webhooks']; + } + + /** + * Sets disabled_webhooks + * + * @param array[]|null $disabled_webhooks Currently configured URLs that are sticky auto-disabled (subset of `webhookUrl`) + * + * @return self + */ + public function setDisabledWebhooks($disabled_webhooks) + { + if (is_null($disabled_webhooks)) { + throw new \InvalidArgumentException('non-nullable disabled_webhooks cannot be null'); + } + $this->container['disabled_webhooks'] = $disabled_webhooks; + + return $this; + } + + /** + * Gets webhook_statuses + * + * @return \OneMsgSdk\Model\WebhookUrlStatus[]|null + */ + public function getWebhookStatuses() + { + return $this->container['webhook_statuses']; + } + + /** + * Sets webhook_statuses + * + * @param \OneMsgSdk\Model\WebhookUrlStatus[]|null $webhook_statuses One entry per currently configured URL. Prefer this over top-level `haveDisabledWebhooks` / `circuitState`. + * + * @return self + */ + public function setWebhookStatuses($webhook_statuses) + { + if (is_null($webhook_statuses)) { + throw new \InvalidArgumentException('non-nullable webhook_statuses cannot be null'); + } + $this->container['webhook_statuses'] = $webhook_statuses; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/ListSettings200ResponseWebhookUrl.php b/lib/Model/ListSettings200ResponseWebhookUrl.php new file mode 100644 index 0000000..f560e23 --- /dev/null +++ b/lib/Model/ListSettings200ResponseWebhookUrl.php @@ -0,0 +1,383 @@ + + */ +class ListSettings200ResponseWebhookUrl implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'listSettings_200_response_webhookUrl'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/ListTemplates200Response.php b/lib/Model/ListTemplates200Response.php index 671ecd7..4c596e9 100644 --- a/lib/Model/ListTemplates200Response.php +++ b/lib/Model/ListTemplates200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/MessageSentResponse.php b/lib/Model/MessageSentResponse.php index e203ea2..139425b 100644 --- a/lib/Model/MessageSentResponse.php +++ b/lib/Model/MessageSentResponse.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/ModelInterface.php b/lib/Model/ModelInterface.php index a2e49db..3f6011c 100644 --- a/lib/Model/ModelInterface.php +++ b/lib/Model/ModelInterface.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/PatchFlowsFlowIdAssets200Response.php b/lib/Model/PatchFlowsFlowIdAssets200Response.php index 9e679c2..80daf17 100644 --- a/lib/Model/PatchFlowsFlowIdAssets200Response.php +++ b/lib/Model/PatchFlowsFlowIdAssets200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/PatchFlowsFlowIdAssetsRequest.php b/lib/Model/PatchFlowsFlowIdAssetsRequest.php new file mode 100644 index 0000000..946103e --- /dev/null +++ b/lib/Model/PatchFlowsFlowIdAssetsRequest.php @@ -0,0 +1,413 @@ + + */ +class PatchFlowsFlowIdAssetsRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'patchFlowsFlowIdAssets_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'flow_json' => 'array' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'flow_json' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'flow_json' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'flow_json' => 'flowJson' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'flow_json' => 'setFlowJson' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'flow_json' => 'getFlowJson' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('flow_json', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['flow_json'] === null) { + $invalidProperties[] = "'flow_json' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets flow_json + * + * @return array + */ + public function getFlowJson() + { + return $this->container['flow_json']; + } + + /** + * Sets flow_json + * + * @param array $flow_json WhatsApp Flow JSON document + * + * @return self + */ + public function setFlowJson($flow_json) + { + if (is_null($flow_json)) { + throw new \InvalidArgumentException('non-nullable flow_json cannot be null'); + } + $this->container['flow_json'] = $flow_json; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/PatchFlowsFlowIdMetadataRequest.php b/lib/Model/PatchFlowsFlowIdMetadataRequest.php new file mode 100644 index 0000000..ff03952 --- /dev/null +++ b/lib/Model/PatchFlowsFlowIdMetadataRequest.php @@ -0,0 +1,514 @@ + + */ +class PatchFlowsFlowIdMetadataRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'patchFlowsFlowIdMetadata_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'name' => 'string', + 'categories' => 'string[]', + 'endpoint_uri' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'name' => null, + 'categories' => null, + 'endpoint_uri' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'name' => false, + 'categories' => false, + 'endpoint_uri' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'name' => 'name', + 'categories' => 'categories', + 'endpoint_uri' => 'endpointUri' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'name' => 'setName', + 'categories' => 'setCategories', + 'endpoint_uri' => 'setEndpointUri' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'name' => 'getName', + 'categories' => 'getCategories', + 'endpoint_uri' => 'getEndpointUri' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const CATEGORIES_SIGN_UP = 'SIGN_UP'; + public const CATEGORIES_SIGN_IN = 'SIGN_IN'; + public const CATEGORIES_APPOINTMENT_BOOKING = 'APPOINTMENT_BOOKING'; + public const CATEGORIES_LEAD_GENERATION = 'LEAD_GENERATION'; + public const CATEGORIES_CONTACT_US = 'CONTACT_US'; + public const CATEGORIES_CUSTOMER_SUPPORT = 'CUSTOMER_SUPPORT'; + public const CATEGORIES_SURVEY = 'SURVEY'; + public const CATEGORIES_OTHER = 'OTHER'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getCategoriesAllowableValues() + { + return [ + self::CATEGORIES_SIGN_UP, + self::CATEGORIES_SIGN_IN, + self::CATEGORIES_APPOINTMENT_BOOKING, + self::CATEGORIES_LEAD_GENERATION, + self::CATEGORIES_CONTACT_US, + self::CATEGORIES_CUSTOMER_SUPPORT, + self::CATEGORIES_SURVEY, + self::CATEGORIES_OTHER, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('name', $data ?? [], null); + $this->setIfExists('categories', $data ?? [], null); + $this->setIfExists('endpoint_uri', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets name + * + * @return string|null + */ + public function getName() + { + return $this->container['name']; + } + + /** + * Sets name + * + * @param string|null $name name + * + * @return self + */ + public function setName($name) + { + if (is_null($name)) { + throw new \InvalidArgumentException('non-nullable name cannot be null'); + } + $this->container['name'] = $name; + + return $this; + } + + /** + * Gets categories + * + * @return string[]|null + */ + public function getCategories() + { + return $this->container['categories']; + } + + /** + * Sets categories + * + * @param string[]|null $categories categories + * + * @return self + */ + public function setCategories($categories) + { + if (is_null($categories)) { + throw new \InvalidArgumentException('non-nullable categories cannot be null'); + } + $allowedValues = $this->getCategoriesAllowableValues(); + if (array_diff($categories, $allowedValues)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value for 'categories', must be one of '%s'", + implode("', '", $allowedValues) + ) + ); + } + $this->container['categories'] = $categories; + + return $this; + } + + /** + * Gets endpoint_uri + * + * @return string|null + */ + public function getEndpointUri() + { + return $this->container['endpoint_uri']; + } + + /** + * Sets endpoint_uri + * + * @param string|null $endpoint_uri HTTPS endpoint for the Flow data channel + * + * @return self + */ + public function setEndpointUri($endpoint_uri) + { + if (is_null($endpoint_uri)) { + throw new \InvalidArgumentException('non-nullable endpoint_uri cannot be null'); + } + $this->container['endpoint_uri'] = $endpoint_uri; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/ProfileInfo.php b/lib/Model/ProfileInfo.php index b0983de..fbdbc00 100644 --- a/lib/Model/ProfileInfo.php +++ b/lib/Model/ProfileInfo.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/RemoveTemplateRequest.php b/lib/Model/RemoveTemplateRequest.php new file mode 100644 index 0000000..ff5f975 --- /dev/null +++ b/lib/Model/RemoveTemplateRequest.php @@ -0,0 +1,413 @@ + + */ +class RemoveTemplateRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'removeTemplate_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'name' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'name' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'name' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'name' => 'name' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'name' => 'setName' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'name' => 'getName' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('name', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['name'] === null) { + $invalidProperties[] = "'name' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets name + * + * @return string + */ + public function getName() + { + return $this->container['name']; + } + + /** + * Sets name + * + * @param string $name Template name to delete + * + * @return self + */ + public function setName($name) + { + if (is_null($name)) { + throw new \InvalidArgumentException('non-nullable name cannot be null'); + } + $this->container['name'] = $name; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/RetrieveMedia200Response.php b/lib/Model/RetrieveMedia200Response.php index 89fc193..663e022 100644 --- a/lib/Model/RetrieveMedia200Response.php +++ b/lib/Model/RetrieveMedia200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendAddressMessageRequest.php b/lib/Model/SendAddressMessageRequest.php index cfd41a9..0096b42 100644 --- a/lib/Model/SendAddressMessageRequest.php +++ b/lib/Model/SendAddressMessageRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendButtonRequest.php b/lib/Model/SendButtonRequest.php index 9deecc4..e2f29f9 100644 --- a/lib/Model/SendButtonRequest.php +++ b/lib/Model/SendButtonRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -59,8 +59,12 @@ class SendButtonRequest implements ModelInterface, ArrayAccess, \JsonSerializabl */ protected static $openAPITypes = [ 'phone' => 'string', + 'chat_id' => 'string', + 'quoted_msg_id' => 'string', 'body' => 'string', 'footer' => 'string', + 'header' => 'array', + 'buttons' => 'object[]', 'sections' => '\OneMsgSdk\Model\SendButtonRequestSectionsInner[]' ]; @@ -73,8 +77,12 @@ class SendButtonRequest implements ModelInterface, ArrayAccess, \JsonSerializabl */ protected static $openAPIFormats = [ 'phone' => null, + 'chat_id' => null, + 'quoted_msg_id' => null, 'body' => null, 'footer' => null, + 'header' => null, + 'buttons' => null, 'sections' => null ]; @@ -85,8 +93,12 @@ class SendButtonRequest implements ModelInterface, ArrayAccess, \JsonSerializabl */ protected static array $openAPINullables = [ 'phone' => false, + 'chat_id' => false, + 'quoted_msg_id' => false, 'body' => false, 'footer' => false, + 'header' => false, + 'buttons' => false, 'sections' => false ]; @@ -177,8 +189,12 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'phone' => 'phone', + 'chat_id' => 'chatId', + 'quoted_msg_id' => 'quotedMsgId', 'body' => 'body', 'footer' => 'footer', + 'header' => 'header', + 'buttons' => 'buttons', 'sections' => 'sections' ]; @@ -189,8 +205,12 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'phone' => 'setPhone', + 'chat_id' => 'setChatId', + 'quoted_msg_id' => 'setQuotedMsgId', 'body' => 'setBody', 'footer' => 'setFooter', + 'header' => 'setHeader', + 'buttons' => 'setButtons', 'sections' => 'setSections' ]; @@ -201,8 +221,12 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'phone' => 'getPhone', + 'chat_id' => 'getChatId', + 'quoted_msg_id' => 'getQuotedMsgId', 'body' => 'getBody', 'footer' => 'getFooter', + 'header' => 'getHeader', + 'buttons' => 'getButtons', 'sections' => 'getSections' ]; @@ -264,8 +288,12 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); + $this->setIfExists('quoted_msg_id', $data ?? [], null); $this->setIfExists('body', $data ?? [], null); $this->setIfExists('footer', $data ?? [], null); + $this->setIfExists('header', $data ?? [], null); + $this->setIfExists('buttons', $data ?? [], null); $this->setIfExists('sections', $data ?? [], null); } @@ -324,7 +352,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone Recipient phone with country code. Use phone or chatId. * * @return self */ @@ -338,6 +366,60 @@ public function setPhone($phone) return $this; } + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + + /** + * Gets quoted_msg_id + * + * @return string|null + */ + public function getQuotedMsgId() + { + return $this->container['quoted_msg_id']; + } + + /** + * Sets quoted_msg_id + * + * @param string|null $quoted_msg_id Optional Cloud API wamid to quote + * + * @return self + */ + public function setQuotedMsgId($quoted_msg_id) + { + if (is_null($quoted_msg_id)) { + throw new \InvalidArgumentException('non-nullable quoted_msg_id cannot be null'); + } + $this->container['quoted_msg_id'] = $quoted_msg_id; + + return $this; + } + /** * Gets body * @@ -392,6 +474,60 @@ public function setFooter($footer) return $this; } + /** + * Gets header + * + * @return array|null + */ + public function getHeader() + { + return $this->container['header']; + } + + /** + * Sets header + * + * @param array|null $header Optional interactive header (text/image/video/document) + * + * @return self + */ + public function setHeader($header) + { + if (is_null($header)) { + throw new \InvalidArgumentException('non-nullable header cannot be null'); + } + $this->container['header'] = $header; + + return $this; + } + + /** + * Gets buttons + * + * @return object[]|null + */ + public function getButtons() + { + return $this->container['buttons']; + } + + /** + * Sets buttons + * + * @param object[]|null $buttons Legacy alias for sections (`[{id,title}]`) + * + * @return self + */ + public function setButtons($buttons) + { + if (is_null($buttons)) { + throw new \InvalidArgumentException('non-nullable buttons cannot be null'); + } + $this->container['buttons'] = $buttons; + + return $this; + } + /** * Gets sections * diff --git a/lib/Model/SendButtonRequestSectionsInner.php b/lib/Model/SendButtonRequestSectionsInner.php index fe8cba2..f301397 100644 --- a/lib/Model/SendButtonRequestSectionsInner.php +++ b/lib/Model/SendButtonRequestSectionsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendButtonRequestSectionsInnerReply.php b/lib/Model/SendButtonRequestSectionsInnerReply.php index 0836a9c..af0f870 100644 --- a/lib/Model/SendButtonRequestSectionsInnerReply.php +++ b/lib/Model/SendButtonRequestSectionsInnerReply.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendCarouselRequest1.php b/lib/Model/SendCarouselRequest1.php index e47a6b3..0caa297 100644 --- a/lib/Model/SendCarouselRequest1.php +++ b/lib/Model/SendCarouselRequest1.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -59,6 +59,7 @@ class SendCarouselRequest1 implements ModelInterface, ArrayAccess, \JsonSerializ */ protected static $openAPITypes = [ 'body' => 'string', + 'cards' => 'array[]', 'params' => 'object[]', 'quoted_msg_id' => 'string', 'chat_id' => 'string', @@ -74,6 +75,7 @@ class SendCarouselRequest1 implements ModelInterface, ArrayAccess, \JsonSerializ */ protected static $openAPIFormats = [ 'body' => null, + 'cards' => null, 'params' => null, 'quoted_msg_id' => null, 'chat_id' => null, @@ -87,6 +89,7 @@ class SendCarouselRequest1 implements ModelInterface, ArrayAccess, \JsonSerializ */ protected static array $openAPINullables = [ 'body' => false, + 'cards' => false, 'params' => false, 'quoted_msg_id' => false, 'chat_id' => false, @@ -180,6 +183,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'body' => 'body', + 'cards' => 'cards', 'params' => 'params', 'quoted_msg_id' => 'quotedMsgId', 'chat_id' => 'chatId', @@ -193,6 +197,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'body' => 'setBody', + 'cards' => 'setCards', 'params' => 'setParams', 'quoted_msg_id' => 'setQuotedMsgId', 'chat_id' => 'setChatId', @@ -206,6 +211,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'body' => 'getBody', + 'cards' => 'getCards', 'params' => 'getParams', 'quoted_msg_id' => 'getQuotedMsgId', 'chat_id' => 'getChatId', @@ -270,6 +276,7 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('body', $data ?? [], null); + $this->setIfExists('cards', $data ?? [], null); $this->setIfExists('params', $data ?? [], null); $this->setIfExists('quoted_msg_id', $data ?? [], null); $this->setIfExists('chat_id', $data ?? [], null); @@ -303,9 +310,14 @@ public function listInvalidProperties() { $invalidProperties = []; - if ($this->container['params'] === null) { - $invalidProperties[] = "'params' can't be null"; + if (!is_null($this->container['cards']) && (count($this->container['cards']) > 10)) { + $invalidProperties[] = "invalid value for 'cards', number of items must be less than or equal to 10."; } + + if (!is_null($this->container['cards']) && (count($this->container['cards']) < 2)) { + $invalidProperties[] = "invalid value for 'cards', number of items must be greater than or equal to 2."; + } + return $invalidProperties; } @@ -334,7 +346,7 @@ public function getBody() /** * Sets body * - * @param string|null $body body + * @param string|null $body Text shown above the carousel * * @return self */ @@ -348,10 +360,44 @@ public function setBody($body) return $this; } + /** + * Gets cards + * + * @return array[]|null + */ + public function getCards() + { + return $this->container['cards']; + } + + /** + * Sets cards + * + * @param array[]|null $cards Preferred. 2–10 carousel cards. + * + * @return self + */ + public function setCards($cards) + { + if (is_null($cards)) { + throw new \InvalidArgumentException('non-nullable cards cannot be null'); + } + + if ((count($cards) > 10)) { + throw new \InvalidArgumentException('invalid value for $cards when calling SendCarouselRequest1., number of items must be less than or equal to 10.'); + } + if ((count($cards) < 2)) { + throw new \InvalidArgumentException('invalid length for $cards when calling SendCarouselRequest1., number of items must be greater than or equal to 2.'); + } + $this->container['cards'] = $cards; + + return $this; + } + /** * Gets params * - * @return object[] + * @return object[]|null */ public function getParams() { @@ -361,7 +407,7 @@ public function getParams() /** * Sets params * - * @param object[] $params params + * @param object[]|null $params Template-like alternative — include a CAROUSEL component with cards * * @return self */ diff --git a/lib/Model/SendContactRequest.php b/lib/Model/SendContactRequest.php index 9aad8e4..ffa1464 100644 --- a/lib/Model/SendContactRequest.php +++ b/lib/Model/SendContactRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -59,6 +59,9 @@ class SendContactRequest implements ModelInterface, ArrayAccess, \JsonSerializab */ protected static $openAPITypes = [ 'phone' => 'string', + 'chat_id' => 'string', + 'quoted_msg_id' => 'string', + 'contact' => '\OneMsgSdk\Model\SendContactRequestContact', 'contacts' => '\OneMsgSdk\Model\SendContactRequestContactsInner[]' ]; @@ -71,6 +74,9 @@ class SendContactRequest implements ModelInterface, ArrayAccess, \JsonSerializab */ protected static $openAPIFormats = [ 'phone' => null, + 'chat_id' => null, + 'quoted_msg_id' => null, + 'contact' => null, 'contacts' => null ]; @@ -81,6 +87,9 @@ class SendContactRequest implements ModelInterface, ArrayAccess, \JsonSerializab */ protected static array $openAPINullables = [ 'phone' => false, + 'chat_id' => false, + 'quoted_msg_id' => false, + 'contact' => false, 'contacts' => false ]; @@ -171,6 +180,9 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'phone' => 'phone', + 'chat_id' => 'chatId', + 'quoted_msg_id' => 'quotedMsgId', + 'contact' => 'contact', 'contacts' => 'contacts' ]; @@ -181,6 +193,9 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'phone' => 'setPhone', + 'chat_id' => 'setChatId', + 'quoted_msg_id' => 'setQuotedMsgId', + 'contact' => 'setContact', 'contacts' => 'setContacts' ]; @@ -191,6 +206,9 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'phone' => 'getPhone', + 'chat_id' => 'getChatId', + 'quoted_msg_id' => 'getQuotedMsgId', + 'contact' => 'getContact', 'contacts' => 'getContacts' ]; @@ -252,6 +270,9 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); + $this->setIfExists('quoted_msg_id', $data ?? [], null); + $this->setIfExists('contact', $data ?? [], null); $this->setIfExists('contacts', $data ?? [], null); } @@ -310,7 +331,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone Recipient phone with country code. Use phone or chatId. * * @return self */ @@ -324,6 +345,87 @@ public function setPhone($phone) return $this; } + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + + /** + * Gets quoted_msg_id + * + * @return string|null + */ + public function getQuotedMsgId() + { + return $this->container['quoted_msg_id']; + } + + /** + * Sets quoted_msg_id + * + * @param string|null $quoted_msg_id quoted_msg_id + * + * @return self + */ + public function setQuotedMsgId($quoted_msg_id) + { + if (is_null($quoted_msg_id)) { + throw new \InvalidArgumentException('non-nullable quoted_msg_id cannot be null'); + } + $this->container['quoted_msg_id'] = $quoted_msg_id; + + return $this; + } + + /** + * Gets contact + * + * @return \OneMsgSdk\Model\SendContactRequestContact|null + */ + public function getContact() + { + return $this->container['contact']; + } + + /** + * Sets contact + * + * @param \OneMsgSdk\Model\SendContactRequestContact|null $contact contact + * + * @return self + */ + public function setContact($contact) + { + if (is_null($contact)) { + throw new \InvalidArgumentException('non-nullable contact cannot be null'); + } + $this->container['contact'] = $contact; + + return $this; + } + /** * Gets contacts * diff --git a/lib/Model/SendContactRequestContact.php b/lib/Model/SendContactRequestContact.php new file mode 100644 index 0000000..3d54a5e --- /dev/null +++ b/lib/Model/SendContactRequestContact.php @@ -0,0 +1,553 @@ + + */ +class SendContactRequestContact implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'sendContact_request_contact'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'name' => '\OneMsgSdk\Model\SendContactRequestContactName', + 'phone' => 'string', + 'email' => 'string', + 'org' => 'array', + 'url' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'name' => null, + 'phone' => null, + 'email' => null, + 'org' => null, + 'url' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'name' => false, + 'phone' => false, + 'email' => false, + 'org' => false, + 'url' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'name' => 'name', + 'phone' => 'phone', + 'email' => 'email', + 'org' => 'org', + 'url' => 'url' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'name' => 'setName', + 'phone' => 'setPhone', + 'email' => 'setEmail', + 'org' => 'setOrg', + 'url' => 'setUrl' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'name' => 'getName', + 'phone' => 'getPhone', + 'email' => 'getEmail', + 'org' => 'getOrg', + 'url' => 'getUrl' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('name', $data ?? [], null); + $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('email', $data ?? [], null); + $this->setIfExists('org', $data ?? [], null); + $this->setIfExists('url', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['name'] === null) { + $invalidProperties[] = "'name' can't be null"; + } + if ($this->container['phone'] === null) { + $invalidProperties[] = "'phone' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets name + * + * @return \OneMsgSdk\Model\SendContactRequestContactName + */ + public function getName() + { + return $this->container['name']; + } + + /** + * Sets name + * + * @param \OneMsgSdk\Model\SendContactRequestContactName $name name + * + * @return self + */ + public function setName($name) + { + if (is_null($name)) { + throw new \InvalidArgumentException('non-nullable name cannot be null'); + } + $this->container['name'] = $name; + + return $this; + } + + /** + * Gets phone + * + * @return string + */ + public function getPhone() + { + return $this->container['phone']; + } + + /** + * Sets phone + * + * @param string $phone Contact phone in E.164 (`+79181976551`), not a timestamp + * + * @return self + */ + public function setPhone($phone) + { + if (is_null($phone)) { + throw new \InvalidArgumentException('non-nullable phone cannot be null'); + } + $this->container['phone'] = $phone; + + return $this; + } + + /** + * Gets email + * + * @return string|null + */ + public function getEmail() + { + return $this->container['email']; + } + + /** + * Sets email + * + * @param string|null $email email + * + * @return self + */ + public function setEmail($email) + { + if (is_null($email)) { + throw new \InvalidArgumentException('non-nullable email cannot be null'); + } + $this->container['email'] = $email; + + return $this; + } + + /** + * Gets org + * + * @return array|null + */ + public function getOrg() + { + return $this->container['org']; + } + + /** + * Sets org + * + * @param array|null $org org + * + * @return self + */ + public function setOrg($org) + { + if (is_null($org)) { + throw new \InvalidArgumentException('non-nullable org cannot be null'); + } + $this->container['org'] = $org; + + return $this; + } + + /** + * Gets url + * + * @return string|null + */ + public function getUrl() + { + return $this->container['url']; + } + + /** + * Sets url + * + * @param string|null $url url + * + * @return self + */ + public function setUrl($url) + { + if (is_null($url)) { + throw new \InvalidArgumentException('non-nullable url cannot be null'); + } + $this->container['url'] = $url; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SendContactRequestContactName.php b/lib/Model/SendContactRequestContactName.php new file mode 100644 index 0000000..5a6a1f3 --- /dev/null +++ b/lib/Model/SendContactRequestContactName.php @@ -0,0 +1,383 @@ + + */ +class SendContactRequestContactName implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'sendContact_request_contact_name'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SendContactRequestContactsInner.php b/lib/Model/SendContactRequestContactsInner.php index 3241e80..4fa3800 100644 --- a/lib/Model/SendContactRequestContactsInner.php +++ b/lib/Model/SendContactRequestContactsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -60,11 +60,11 @@ class SendContactRequestContactsInner implements ModelInterface, ArrayAccess, \J protected static $openAPITypes = [ 'name' => '\OneMsgSdk\Model\SendContactRequestContactsInnerName', 'birthday' => 'string', - 'addresses' => 'mixed[]', - 'emails' => 'mixed[]', + 'addresses' => 'array[]', + 'emails' => 'array[]', 'org' => '\OneMsgSdk\Model\SendContactRequestContactsInnerOrg', 'phones' => '\OneMsgSdk\Model\SendContactRequestContactsInnerPhonesInner[]', - 'urls' => 'mixed[]' + 'urls' => 'array[]' ]; /** @@ -389,7 +389,7 @@ public function setBirthday($birthday) /** * Gets addresses * - * @return mixed[]|null + * @return array[]|null */ public function getAddresses() { @@ -399,7 +399,7 @@ public function getAddresses() /** * Sets addresses * - * @param mixed[]|null $addresses addresses + * @param array[]|null $addresses addresses * * @return self */ @@ -416,7 +416,7 @@ public function setAddresses($addresses) /** * Gets emails * - * @return mixed[]|null + * @return array[]|null */ public function getEmails() { @@ -426,7 +426,7 @@ public function getEmails() /** * Sets emails * - * @param mixed[]|null $emails emails + * @param array[]|null $emails emails * * @return self */ @@ -497,7 +497,7 @@ public function setPhones($phones) /** * Gets urls * - * @return mixed[]|null + * @return array[]|null */ public function getUrls() { @@ -507,7 +507,7 @@ public function getUrls() /** * Sets urls * - * @param mixed[]|null $urls urls + * @param array[]|null $urls urls * * @return self */ diff --git a/lib/Model/SendContactRequestContactsInnerName.php b/lib/Model/SendContactRequestContactsInnerName.php index d43ca71..61eaba7 100644 --- a/lib/Model/SendContactRequestContactsInnerName.php +++ b/lib/Model/SendContactRequestContactsInnerName.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendContactRequestContactsInnerOrg.php b/lib/Model/SendContactRequestContactsInnerOrg.php index c258356..e7ef697 100644 --- a/lib/Model/SendContactRequestContactsInnerOrg.php +++ b/lib/Model/SendContactRequestContactsInnerOrg.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendContactRequestContactsInnerPhonesInner.php b/lib/Model/SendContactRequestContactsInnerPhonesInner.php index 2713f98..fc46adf 100644 --- a/lib/Model/SendContactRequestContactsInnerPhonesInner.php +++ b/lib/Model/SendContactRequestContactsInnerPhonesInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -71,7 +71,7 @@ class SendContactRequestContactsInnerPhonesInner implements ModelInterface, Arra * @psalm-var array */ protected static $openAPIFormats = [ - 'phone' => 'utc-millisec', + 'phone' => null, 'type' => null, 'wa_id' => null ]; @@ -317,7 +317,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone E.164 phone (`+79181976551`). Not a timestamp. * * @return self */ diff --git a/lib/Model/SendCtaUrlRequest.php b/lib/Model/SendCtaUrlRequest.php index 9b46cfc..767cfd5 100644 --- a/lib/Model/SendCtaUrlRequest.php +++ b/lib/Model/SendCtaUrlRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendFileRequest.php b/lib/Model/SendFileRequest.php index 7680a83..83527ef 100644 --- a/lib/Model/SendFileRequest.php +++ b/lib/Model/SendFileRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendFlowRequest1.php b/lib/Model/SendFlowRequest1.php index 0d3e0d7..0eccdc7 100644 --- a/lib/Model/SendFlowRequest1.php +++ b/lib/Model/SendFlowRequest1.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendFlowRequest1Header.php b/lib/Model/SendFlowRequest1Header.php index 74310aa..0f68299 100644 --- a/lib/Model/SendFlowRequest1Header.php +++ b/lib/Model/SendFlowRequest1Header.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendFlowRequestHeader.php b/lib/Model/SendFlowRequestHeader.php index fc531a6..629c478 100644 --- a/lib/Model/SendFlowRequestHeader.php +++ b/lib/Model/SendFlowRequestHeader.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendListRequest.php b/lib/Model/SendListRequest.php index cf3a249..197078b 100644 --- a/lib/Model/SendListRequest.php +++ b/lib/Model/SendListRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -59,6 +59,8 @@ class SendListRequest implements ModelInterface, ArrayAccess, \JsonSerializable */ protected static $openAPITypes = [ 'phone' => 'string', + 'chat_id' => 'string', + 'quoted_msg_id' => 'string', 'body' => 'string', 'button_text' => 'string', 'action' => 'string', @@ -74,6 +76,8 @@ class SendListRequest implements ModelInterface, ArrayAccess, \JsonSerializable */ protected static $openAPIFormats = [ 'phone' => null, + 'chat_id' => null, + 'quoted_msg_id' => null, 'body' => null, 'button_text' => null, 'action' => null, @@ -87,6 +91,8 @@ class SendListRequest implements ModelInterface, ArrayAccess, \JsonSerializable */ protected static array $openAPINullables = [ 'phone' => false, + 'chat_id' => false, + 'quoted_msg_id' => false, 'body' => false, 'button_text' => false, 'action' => false, @@ -180,6 +186,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'phone' => 'phone', + 'chat_id' => 'chatId', + 'quoted_msg_id' => 'quotedMsgId', 'body' => 'body', 'button_text' => 'buttonText', 'action' => 'action', @@ -193,6 +201,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'phone' => 'setPhone', + 'chat_id' => 'setChatId', + 'quoted_msg_id' => 'setQuotedMsgId', 'body' => 'setBody', 'button_text' => 'setButtonText', 'action' => 'setAction', @@ -206,6 +216,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'phone' => 'getPhone', + 'chat_id' => 'getChatId', + 'quoted_msg_id' => 'getQuotedMsgId', 'body' => 'getBody', 'button_text' => 'getButtonText', 'action' => 'getAction', @@ -270,6 +282,8 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); + $this->setIfExists('quoted_msg_id', $data ?? [], null); $this->setIfExists('body', $data ?? [], null); $this->setIfExists('button_text', $data ?? [], null); $this->setIfExists('action', $data ?? [], null); @@ -331,7 +345,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone Recipient phone with country code. Use phone or chatId. * * @return self */ @@ -345,6 +359,60 @@ public function setPhone($phone) return $this; } + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + + /** + * Gets quoted_msg_id + * + * @return string|null + */ + public function getQuotedMsgId() + { + return $this->container['quoted_msg_id']; + } + + /** + * Sets quoted_msg_id + * + * @param string|null $quoted_msg_id quoted_msg_id + * + * @return self + */ + public function setQuotedMsgId($quoted_msg_id) + { + if (is_null($quoted_msg_id)) { + throw new \InvalidArgumentException('non-nullable quoted_msg_id cannot be null'); + } + $this->container['quoted_msg_id'] = $quoted_msg_id; + + return $this; + } + /** * Gets body * diff --git a/lib/Model/SendListRequestSectionsInner.php b/lib/Model/SendListRequestSectionsInner.php index 5f1f9ce..da1e342 100644 --- a/lib/Model/SendListRequestSectionsInner.php +++ b/lib/Model/SendListRequestSectionsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendListRequestSectionsInnerRowsInner.php b/lib/Model/SendListRequestSectionsInnerRowsInner.php index c078117..ee8ef3c 100644 --- a/lib/Model/SendListRequestSectionsInnerRowsInner.php +++ b/lib/Model/SendListRequestSectionsInnerRowsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendLocationRequest1.php b/lib/Model/SendLocationRequest1.php index 0e97e40..cb51d64 100644 --- a/lib/Model/SendLocationRequest1.php +++ b/lib/Model/SendLocationRequest1.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendLocationRequestRequest.php b/lib/Model/SendLocationRequestRequest.php index f35e31e..489ec7c 100644 --- a/lib/Model/SendLocationRequestRequest.php +++ b/lib/Model/SendLocationRequestRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -59,6 +59,8 @@ class SendLocationRequestRequest implements ModelInterface, ArrayAccess, \JsonSe */ protected static $openAPITypes = [ 'phone' => 'string', + 'chat_id' => 'string', + 'quoted_msg_id' => 'string', 'body' => 'string' ]; @@ -71,6 +73,8 @@ class SendLocationRequestRequest implements ModelInterface, ArrayAccess, \JsonSe */ protected static $openAPIFormats = [ 'phone' => null, + 'chat_id' => null, + 'quoted_msg_id' => null, 'body' => null ]; @@ -81,6 +85,8 @@ class SendLocationRequestRequest implements ModelInterface, ArrayAccess, \JsonSe */ protected static array $openAPINullables = [ 'phone' => false, + 'chat_id' => false, + 'quoted_msg_id' => false, 'body' => false ]; @@ -171,6 +177,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'phone' => 'phone', + 'chat_id' => 'chatId', + 'quoted_msg_id' => 'quotedMsgId', 'body' => 'body' ]; @@ -181,6 +189,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'phone' => 'setPhone', + 'chat_id' => 'setChatId', + 'quoted_msg_id' => 'setQuotedMsgId', 'body' => 'setBody' ]; @@ -191,6 +201,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'phone' => 'getPhone', + 'chat_id' => 'getChatId', + 'quoted_msg_id' => 'getQuotedMsgId', 'body' => 'getBody' ]; @@ -252,6 +264,8 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); + $this->setIfExists('quoted_msg_id', $data ?? [], null); $this->setIfExists('body', $data ?? [], null); } @@ -310,7 +324,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone Recipient phone with country code. Use phone or chatId. * * @return self */ @@ -324,6 +338,60 @@ public function setPhone($phone) return $this; } + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + + /** + * Gets quoted_msg_id + * + * @return string|null + */ + public function getQuotedMsgId() + { + return $this->container['quoted_msg_id']; + } + + /** + * Sets quoted_msg_id + * + * @param string|null $quoted_msg_id quoted_msg_id + * + * @return self + */ + public function setQuotedMsgId($quoted_msg_id) + { + if (is_null($quoted_msg_id)) { + throw new \InvalidArgumentException('non-nullable quoted_msg_id cannot be null'); + } + $this->container['quoted_msg_id'] = $quoted_msg_id; + + return $this; + } + /** * Gets body * diff --git a/lib/Model/SendMessageRequest.php b/lib/Model/SendMessageRequest.php index 919b713..17f9b86 100644 --- a/lib/Model/SendMessageRequest.php +++ b/lib/Model/SendMessageRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -312,10 +312,6 @@ public function listInvalidProperties() $invalidProperties[] = "invalid value for 'quoted_msg_id', must be conform to the pattern /^wamid\\..+$/."; } - if (!is_null($this->container['chat_id']) && !preg_match("/^[0-9]+@[cg]\\.us$/", $this->container['chat_id'])) { - $invalidProperties[] = "invalid value for 'chat_id', must be conform to the pattern /^[0-9]+@[cg]\\.us$/."; - } - if (!is_null($this->container['phone']) && ($this->container['phone'] > 999999999999999)) { $invalidProperties[] = "invalid value for 'phone', must be smaller than or equal to 999999999999999."; } @@ -418,7 +414,7 @@ public function getChatId() /** * Sets chat_id * - * @param string|null $chat_id Chat ID in format: phone@c.us (individual) or phone@g.us (group) + * @param string|null $chat_id Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid * * @return self */ @@ -427,11 +423,6 @@ public function setChatId($chat_id) if (is_null($chat_id)) { throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); } - - if ((!preg_match("/^[0-9]+@[cg]\\.us$/", ObjectSerializer::toString($chat_id)))) { - throw new \InvalidArgumentException("invalid value for \$chat_id when calling SendMessageRequest., must conform to the pattern /^[0-9]+@[cg]\\.us$/."); - } - $this->container['chat_id'] = $chat_id; return $this; diff --git a/lib/Model/SendOrderDetailsRequest.php b/lib/Model/SendOrderDetailsRequest.php index 91f59d6..47027ba 100644 --- a/lib/Model/SendOrderDetailsRequest.php +++ b/lib/Model/SendOrderDetailsRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendOrderDetailsRequestLanguage.php b/lib/Model/SendOrderDetailsRequestLanguage.php index 227cd1a..2153556 100644 --- a/lib/Model/SendOrderDetailsRequestLanguage.php +++ b/lib/Model/SendOrderDetailsRequestLanguage.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendOrderDetailsRequestOrder.php b/lib/Model/SendOrderDetailsRequestOrder.php index ece5f4a..b4d93c3 100644 --- a/lib/Model/SendOrderDetailsRequestOrder.php +++ b/lib/Model/SendOrderDetailsRequestOrder.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendOrderDetailsRequestOrderItemsInner.php b/lib/Model/SendOrderDetailsRequestOrderItemsInner.php index e05c8ee..98c3add 100644 --- a/lib/Model/SendOrderDetailsRequestOrderItemsInner.php +++ b/lib/Model/SendOrderDetailsRequestOrderItemsInner.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendOrderDetailsRequestOrderItemsInnerAmount.php b/lib/Model/SendOrderDetailsRequestOrderItemsInnerAmount.php index c9fa2c2..882240f 100644 --- a/lib/Model/SendOrderDetailsRequestOrderItemsInnerAmount.php +++ b/lib/Model/SendOrderDetailsRequestOrderItemsInnerAmount.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendPaymentRequestRequest.php b/lib/Model/SendPaymentRequestRequest.php index b4bdc59..05248ca 100644 --- a/lib/Model/SendPaymentRequestRequest.php +++ b/lib/Model/SendPaymentRequestRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendProductRequest.php b/lib/Model/SendProductRequest.php new file mode 100644 index 0000000..636a57a --- /dev/null +++ b/lib/Model/SendProductRequest.php @@ -0,0 +1,617 @@ + + */ +class SendProductRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'sendProduct_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'phone' => 'string', + 'chat_id' => 'string', + 'quoted_msg_id' => 'string', + 'body' => 'string', + 'footer' => 'string', + 'header' => 'string', + 'action' => 'array' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'phone' => null, + 'chat_id' => null, + 'quoted_msg_id' => null, + 'body' => null, + 'footer' => null, + 'header' => null, + 'action' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'phone' => false, + 'chat_id' => false, + 'quoted_msg_id' => false, + 'body' => false, + 'footer' => false, + 'header' => false, + 'action' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'phone' => 'phone', + 'chat_id' => 'chatId', + 'quoted_msg_id' => 'quotedMsgId', + 'body' => 'body', + 'footer' => 'footer', + 'header' => 'header', + 'action' => 'action' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'phone' => 'setPhone', + 'chat_id' => 'setChatId', + 'quoted_msg_id' => 'setQuotedMsgId', + 'body' => 'setBody', + 'footer' => 'setFooter', + 'header' => 'setHeader', + 'action' => 'setAction' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'phone' => 'getPhone', + 'chat_id' => 'getChatId', + 'quoted_msg_id' => 'getQuotedMsgId', + 'body' => 'getBody', + 'footer' => 'getFooter', + 'header' => 'getHeader', + 'action' => 'getAction' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); + $this->setIfExists('quoted_msg_id', $data ?? [], null); + $this->setIfExists('body', $data ?? [], null); + $this->setIfExists('footer', $data ?? [], null); + $this->setIfExists('header', $data ?? [], null); + $this->setIfExists('action', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['action'] === null) { + $invalidProperties[] = "'action' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets phone + * + * @return string|null + */ + public function getPhone() + { + return $this->container['phone']; + } + + /** + * Sets phone + * + * @param string|null $phone Recipient phone with country code. Use phone or chatId. + * + * @return self + */ + public function setPhone($phone) + { + if (is_null($phone)) { + throw new \InvalidArgumentException('non-nullable phone cannot be null'); + } + $this->container['phone'] = $phone; + + return $this; + } + + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + + /** + * Gets quoted_msg_id + * + * @return string|null + */ + public function getQuotedMsgId() + { + return $this->container['quoted_msg_id']; + } + + /** + * Sets quoted_msg_id + * + * @param string|null $quoted_msg_id quoted_msg_id + * + * @return self + */ + public function setQuotedMsgId($quoted_msg_id) + { + if (is_null($quoted_msg_id)) { + throw new \InvalidArgumentException('non-nullable quoted_msg_id cannot be null'); + } + $this->container['quoted_msg_id'] = $quoted_msg_id; + + return $this; + } + + /** + * Gets body + * + * @return string|null + */ + public function getBody() + { + return $this->container['body']; + } + + /** + * Sets body + * + * @param string|null $body Optional message text shown with the product + * + * @return self + */ + public function setBody($body) + { + if (is_null($body)) { + throw new \InvalidArgumentException('non-nullable body cannot be null'); + } + $this->container['body'] = $body; + + return $this; + } + + /** + * Gets footer + * + * @return string|null + */ + public function getFooter() + { + return $this->container['footer']; + } + + /** + * Sets footer + * + * @param string|null $footer footer + * + * @return self + */ + public function setFooter($footer) + { + if (is_null($footer)) { + throw new \InvalidArgumentException('non-nullable footer cannot be null'); + } + $this->container['footer'] = $footer; + + return $this; + } + + /** + * Gets header + * + * @return string|null + */ + public function getHeader() + { + return $this->container['header']; + } + + /** + * Sets header + * + * @param string|null $header Text header (product list only) + * + * @return self + */ + public function setHeader($header) + { + if (is_null($header)) { + throw new \InvalidArgumentException('non-nullable header cannot be null'); + } + $this->container['header'] = $header; + + return $this; + } + + /** + * Gets action + * + * @return array + */ + public function getAction() + { + return $this->container['action']; + } + + /** + * Sets action + * + * @param array $action Catalog action (`catalog_id` + `product_retailer_id`, or `sections` for a list) + * + * @return self + */ + public function setAction($action) + { + if (is_null($action)) { + throw new \InvalidArgumentException('non-nullable action cannot be null'); + } + $this->container['action'] = $action; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SendReactionRequest.php b/lib/Model/SendReactionRequest.php index e0bd76e..7401e9c 100644 --- a/lib/Model/SendReactionRequest.php +++ b/lib/Model/SendReactionRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -59,6 +59,7 @@ class SendReactionRequest implements ModelInterface, ArrayAccess, \JsonSerializa */ protected static $openAPITypes = [ 'phone' => 'string', + 'chat_id' => 'string', 'body' => 'string', 'quoted_msg_id' => 'string' ]; @@ -72,6 +73,7 @@ class SendReactionRequest implements ModelInterface, ArrayAccess, \JsonSerializa */ protected static $openAPIFormats = [ 'phone' => null, + 'chat_id' => null, 'body' => null, 'quoted_msg_id' => null ]; @@ -83,6 +85,7 @@ class SendReactionRequest implements ModelInterface, ArrayAccess, \JsonSerializa */ protected static array $openAPINullables = [ 'phone' => false, + 'chat_id' => false, 'body' => false, 'quoted_msg_id' => false ]; @@ -174,6 +177,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'phone' => 'phone', + 'chat_id' => 'chatId', 'body' => 'body', 'quoted_msg_id' => 'quotedMsgId' ]; @@ -185,6 +189,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'phone' => 'setPhone', + 'chat_id' => 'setChatId', 'body' => 'setBody', 'quoted_msg_id' => 'setQuotedMsgId' ]; @@ -196,6 +201,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'phone' => 'getPhone', + 'chat_id' => 'getChatId', 'body' => 'getBody', 'quoted_msg_id' => 'getQuotedMsgId' ]; @@ -258,6 +264,7 @@ public function getModelName() public function __construct(?array $data = null) { $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); $this->setIfExists('body', $data ?? [], null); $this->setIfExists('quoted_msg_id', $data ?? [], null); } @@ -317,7 +324,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone Recipient phone with country code. Use phone or chatId. * * @return self */ @@ -331,6 +338,33 @@ public function setPhone($phone) return $this; } + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + /** * Gets body * diff --git a/lib/Model/SendStickerRequest.php b/lib/Model/SendStickerRequest.php index c6a2f5b..7c186a3 100644 --- a/lib/Model/SendStickerRequest.php +++ b/lib/Model/SendStickerRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SendTemplateRequest.php b/lib/Model/SendTemplateRequest.php index 7047d4c..1a18898 100644 --- a/lib/Model/SendTemplateRequest.php +++ b/lib/Model/SendTemplateRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -61,8 +61,10 @@ class SendTemplateRequest implements ModelInterface, ArrayAccess, \JsonSerializa 'template' => 'string', 'language' => '\OneMsgSdk\Model\SendTemplateRequestLanguage', 'namespace' => 'string', - 'params' => 'mixed[]', + 'params' => 'array[]', 'phone' => 'string', + 'chat_id' => 'string', + 'quoted_msg_id' => 'string', 'use_m_mlite' => 'bool', 'message_activity_sharing' => 'bool', 'message_send_ttl_seconds' => 'int' @@ -81,6 +83,8 @@ class SendTemplateRequest implements ModelInterface, ArrayAccess, \JsonSerializa 'namespace' => null, 'params' => null, 'phone' => null, + 'chat_id' => null, + 'quoted_msg_id' => null, 'use_m_mlite' => null, 'message_activity_sharing' => null, 'message_send_ttl_seconds' => null @@ -97,6 +101,8 @@ class SendTemplateRequest implements ModelInterface, ArrayAccess, \JsonSerializa 'namespace' => false, 'params' => false, 'phone' => false, + 'chat_id' => false, + 'quoted_msg_id' => false, 'use_m_mlite' => false, 'message_activity_sharing' => false, 'message_send_ttl_seconds' => false @@ -193,6 +199,8 @@ public function isNullableSetToNull(string $property): bool 'namespace' => 'namespace', 'params' => 'params', 'phone' => 'phone', + 'chat_id' => 'chatId', + 'quoted_msg_id' => 'quotedMsgId', 'use_m_mlite' => 'useMMlite', 'message_activity_sharing' => 'messageActivitySharing', 'message_send_ttl_seconds' => 'messageSendTtlSeconds' @@ -209,6 +217,8 @@ public function isNullableSetToNull(string $property): bool 'namespace' => 'setNamespace', 'params' => 'setParams', 'phone' => 'setPhone', + 'chat_id' => 'setChatId', + 'quoted_msg_id' => 'setQuotedMsgId', 'use_m_mlite' => 'setUseMMlite', 'message_activity_sharing' => 'setMessageActivitySharing', 'message_send_ttl_seconds' => 'setMessageSendTtlSeconds' @@ -225,6 +235,8 @@ public function isNullableSetToNull(string $property): bool 'namespace' => 'getNamespace', 'params' => 'getParams', 'phone' => 'getPhone', + 'chat_id' => 'getChatId', + 'quoted_msg_id' => 'getQuotedMsgId', 'use_m_mlite' => 'getUseMMlite', 'message_activity_sharing' => 'getMessageActivitySharing', 'message_send_ttl_seconds' => 'getMessageSendTtlSeconds' @@ -292,6 +304,8 @@ public function __construct(?array $data = null) $this->setIfExists('namespace', $data ?? [], null); $this->setIfExists('params', $data ?? [], null); $this->setIfExists('phone', $data ?? [], null); + $this->setIfExists('chat_id', $data ?? [], null); + $this->setIfExists('quoted_msg_id', $data ?? [], null); $this->setIfExists('use_m_mlite', $data ?? [], null); $this->setIfExists('message_activity_sharing', $data ?? [], null); $this->setIfExists('message_send_ttl_seconds', $data ?? [], null); @@ -352,7 +366,7 @@ public function getTemplate() /** * Sets template * - * @param string|null $template template + * @param string|null $template Approved template name (template `name`, not the old HSM `element_name`) * * @return self */ @@ -406,7 +420,7 @@ public function getNamespace() /** * Sets namespace * - * @param string|null $namespace namespace + * @param string|null $namespace Template namespace from `list_templates` * * @return self */ @@ -423,7 +437,7 @@ public function setNamespace($namespace) /** * Gets params * - * @return mixed[]|null + * @return array[]|null */ public function getParams() { @@ -433,7 +447,7 @@ public function getParams() /** * Sets params * - * @param mixed[]|null $params params + * @param array[]|null $params Template components in WhatsApp Cloud API format. Each item is an object with `type` (`body`, `header`, `button`) and `parameters` (objects with `type` + `text` / media / action). Never a list of strings. Never HSM `localizable_params`. * * @return self */ @@ -460,7 +474,7 @@ public function getPhone() /** * Sets phone * - * @param string|null $phone phone + * @param string|null $phone Recipient phone with country code (use phone or chatId) * * @return self */ @@ -474,6 +488,60 @@ public function setPhone($phone) return $this; } + /** + * Gets chat_id + * + * @return string|null + */ + public function getChatId() + { + return $this->container['chat_id']; + } + + /** + * Sets chat_id + * + * @param string|null $chat_id Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). Use phone or chatId. + * + * @return self + */ + public function setChatId($chat_id) + { + if (is_null($chat_id)) { + throw new \InvalidArgumentException('non-nullable chat_id cannot be null'); + } + $this->container['chat_id'] = $chat_id; + + return $this; + } + + /** + * Gets quoted_msg_id + * + * @return string|null + */ + public function getQuotedMsgId() + { + return $this->container['quoted_msg_id']; + } + + /** + * Sets quoted_msg_id + * + * @param string|null $quoted_msg_id Optional Cloud API wamid to quote + * + * @return self + */ + public function setQuotedMsgId($quoted_msg_id) + { + if (is_null($quoted_msg_id)) { + throw new \InvalidArgumentException('non-nullable quoted_msg_id cannot be null'); + } + $this->container['quoted_msg_id'] = $quoted_msg_id; + + return $this; + } + /** * Gets use_m_mlite * diff --git a/lib/Model/SendTemplateRequestLanguage.php b/lib/Model/SendTemplateRequestLanguage.php index 62ba348..2d2e996 100644 --- a/lib/Model/SendTemplateRequestLanguage.php +++ b/lib/Model/SendTemplateRequestLanguage.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -36,6 +36,7 @@ * SendTemplateRequestLanguage Class Doc Comment * * @category Class + * @description Template language. Always set `policy` to `deterministic`. * @package OneMsgSdk * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -251,7 +252,7 @@ public function getModelName() */ public function __construct(?array $data = null) { - $this->setIfExists('policy', $data ?? [], null); + $this->setIfExists('policy', $data ?? [], 'deterministic'); $this->setIfExists('code', $data ?? [], null); } @@ -310,7 +311,7 @@ public function getPolicy() /** * Sets policy * - * @param string|null $policy policy + * @param string|null $policy Must be `deterministic` * * @return self */ diff --git a/lib/Model/SetWebhook200Response.php b/lib/Model/SetWebhook200Response.php new file mode 100644 index 0000000..fe78f19 --- /dev/null +++ b/lib/Model/SetWebhook200Response.php @@ -0,0 +1,444 @@ + + */ +class SetWebhook200Response implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'setWebhook_200_response'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'set' => 'bool', + 'webhook_url' => '\OneMsgSdk\Model\SetWebhook200ResponseWebhookUrl' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'set' => null, + 'webhook_url' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'set' => false, + 'webhook_url' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'set' => 'set', + 'webhook_url' => 'webhookUrl' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'set' => 'setSet', + 'webhook_url' => 'setWebhookUrl' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'set' => 'getSet', + 'webhook_url' => 'getWebhookUrl' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('set', $data ?? [], null); + $this->setIfExists('webhook_url', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets set + * + * @return bool|null + */ + public function getSet() + { + return $this->container['set']; + } + + /** + * Sets set + * + * @param bool|null $set True when the stored webhook URL(s) match the request + * + * @return self + */ + public function setSet($set) + { + if (is_null($set)) { + throw new \InvalidArgumentException('non-nullable set cannot be null'); + } + $this->container['set'] = $set; + + return $this; + } + + /** + * Gets webhook_url + * + * @return \OneMsgSdk\Model\SetWebhook200ResponseWebhookUrl|null + */ + public function getWebhookUrl() + { + return $this->container['webhook_url']; + } + + /** + * Sets webhook_url + * + * @param \OneMsgSdk\Model\SetWebhook200ResponseWebhookUrl|null $webhook_url webhook_url + * + * @return self + */ + public function setWebhookUrl($webhook_url) + { + if (is_null($webhook_url)) { + throw new \InvalidArgumentException('non-nullable webhook_url cannot be null'); + } + $this->container['webhook_url'] = $webhook_url; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SetWebhook200ResponseWebhookUrl.php b/lib/Model/SetWebhook200ResponseWebhookUrl.php new file mode 100644 index 0000000..80dfde2 --- /dev/null +++ b/lib/Model/SetWebhook200ResponseWebhookUrl.php @@ -0,0 +1,383 @@ + + */ +class SetWebhook200ResponseWebhookUrl implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'setWebhook_200_response_webhookUrl'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SetWebhookRequest.php b/lib/Model/SetWebhookRequest.php new file mode 100644 index 0000000..37a4803 --- /dev/null +++ b/lib/Model/SetWebhookRequest.php @@ -0,0 +1,413 @@ + + */ +class SetWebhookRequest implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'setWebhook_request'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'webhook_url' => '\OneMsgSdk\Model\SetWebhookRequestWebhookUrl' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'webhook_url' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'webhook_url' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'webhook_url' => 'webhookUrl' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'webhook_url' => 'setWebhookUrl' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'webhook_url' => 'getWebhookUrl' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('webhook_url', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['webhook_url'] === null) { + $invalidProperties[] = "'webhook_url' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets webhook_url + * + * @return \OneMsgSdk\Model\SetWebhookRequestWebhookUrl + */ + public function getWebhookUrl() + { + return $this->container['webhook_url']; + } + + /** + * Sets webhook_url + * + * @param \OneMsgSdk\Model\SetWebhookRequestWebhookUrl $webhook_url webhook_url + * + * @return self + */ + public function setWebhookUrl($webhook_url) + { + if (is_null($webhook_url)) { + throw new \InvalidArgumentException('non-nullable webhook_url cannot be null'); + } + $this->container['webhook_url'] = $webhook_url; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SetWebhookRequestWebhookUrl.php b/lib/Model/SetWebhookRequestWebhookUrl.php new file mode 100644 index 0000000..54ff0f7 --- /dev/null +++ b/lib/Model/SetWebhookRequestWebhookUrl.php @@ -0,0 +1,383 @@ + + */ +class SetWebhookRequestWebhookUrl implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'setWebhook_request_webhookUrl'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/SetWhatsappBusinessEncryptionRequest.php b/lib/Model/SetWhatsappBusinessEncryptionRequest.php index 1a2f46b..238bcf5 100644 --- a/lib/Model/SetWhatsappBusinessEncryptionRequest.php +++ b/lib/Model/SetWhatsappBusinessEncryptionRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/SuccessResponse.php b/lib/Model/SuccessResponse.php index 63573dd..d88a56c 100644 --- a/lib/Model/SuccessResponse.php +++ b/lib/Model/SuccessResponse.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/UpdateCallingSettings200Response.php b/lib/Model/UpdateCallingSettings200Response.php index 5f9912d..bcfbf10 100644 --- a/lib/Model/UpdateCallingSettings200Response.php +++ b/lib/Model/UpdateCallingSettings200Response.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/UpdateCallingSettings200ResponseResponse.php b/lib/Model/UpdateCallingSettings200ResponseResponse.php index 1421b6c..6f73b03 100644 --- a/lib/Model/UpdateCallingSettings200ResponseResponse.php +++ b/lib/Model/UpdateCallingSettings200ResponseResponse.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/lib/Model/UpdateMe200Response.php b/lib/Model/UpdateMe200Response.php new file mode 100644 index 0000000..3d9f445 --- /dev/null +++ b/lib/Model/UpdateMe200Response.php @@ -0,0 +1,410 @@ + + */ +class UpdateMe200Response implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'updateMe_200_response'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'result' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'result' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'result' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'result' => 'result' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'result' => 'setResult' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'result' => 'getResult' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('result', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets result + * + * @return string|null + */ + public function getResult() + { + return $this->container['result']; + } + + /** + * Sets result + * + * @param string|null $result result + * + * @return self + */ + public function setResult($result) + { + if (is_null($result)) { + throw new \InvalidArgumentException('non-nullable result cannot be null'); + } + $this->container['result'] = $result; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/Model/UpdateMeRequest.php b/lib/Model/UpdateMeRequest.php index 0617be6..40e82f4 100644 --- a/lib/Model/UpdateMeRequest.php +++ b/lib/Model/UpdateMeRequest.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io @@ -345,7 +345,7 @@ public function getAbout() /** * Sets about * - * @param string|null $about Profile About section + * @param string|null $about Profile About section (WhatsApp status text) * * @return self */ @@ -372,7 +372,7 @@ public function getAddress() /** * Sets address * - * @param string|null $address address + * @param string|null $address Business address shown on the profile * * @return self */ @@ -426,7 +426,7 @@ public function getEmail() /** * Sets email * - * @param string|null $email email + * @param string|null $email Business email shown on the profile * * @return self */ @@ -507,7 +507,7 @@ public function getWebsites() /** * Sets websites * - * @param string[]|null $websites websites + * @param string[]|null $websites Business website URLs * * @return self */ diff --git a/lib/Model/WebhookUrlStatus.php b/lib/Model/WebhookUrlStatus.php new file mode 100644 index 0000000..ed88377 --- /dev/null +++ b/lib/Model/WebhookUrlStatus.php @@ -0,0 +1,729 @@ + + */ +class WebhookUrlStatus implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'WebhookUrlStatus'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'url' => 'string', + 'circuit_state' => 'string', + 'reason' => 'string', + 'fails' => 'int', + 'trips' => 'int', + 'disabled_at' => 'int', + 'first_fail_at' => 'int', + 'last_status' => 'int', + 'next_probe_at' => 'int' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'url' => 'uri', + 'circuit_state' => null, + 'reason' => null, + 'fails' => null, + 'trips' => null, + 'disabled_at' => null, + 'first_fail_at' => null, + 'last_status' => null, + 'next_probe_at' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'url' => false, + 'circuit_state' => false, + 'reason' => false, + 'fails' => false, + 'trips' => false, + 'disabled_at' => false, + 'first_fail_at' => false, + 'last_status' => false, + 'next_probe_at' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'url' => 'url', + 'circuit_state' => 'circuitState', + 'reason' => 'reason', + 'fails' => 'fails', + 'trips' => 'trips', + 'disabled_at' => 'disabledAt', + 'first_fail_at' => 'firstFailAt', + 'last_status' => 'lastStatus', + 'next_probe_at' => 'nextProbeAt' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'url' => 'setUrl', + 'circuit_state' => 'setCircuitState', + 'reason' => 'setReason', + 'fails' => 'setFails', + 'trips' => 'setTrips', + 'disabled_at' => 'setDisabledAt', + 'first_fail_at' => 'setFirstFailAt', + 'last_status' => 'setLastStatus', + 'next_probe_at' => 'setNextProbeAt' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'url' => 'getUrl', + 'circuit_state' => 'getCircuitState', + 'reason' => 'getReason', + 'fails' => 'getFails', + 'trips' => 'getTrips', + 'disabled_at' => 'getDisabledAt', + 'first_fail_at' => 'getFirstFailAt', + 'last_status' => 'getLastStatus', + 'next_probe_at' => 'getNextProbeAt' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const CIRCUIT_STATE_OK = 'ok'; + public const CIRCUIT_STATE_DEGRADED = 'degraded'; + public const CIRCUIT_STATE_OPEN = 'open'; + public const CIRCUIT_STATE_HALF_OPEN = 'half_open'; + public const CIRCUIT_STATE_DISABLED = 'disabled'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getCircuitStateAllowableValues() + { + return [ + self::CIRCUIT_STATE_OK, + self::CIRCUIT_STATE_DEGRADED, + self::CIRCUIT_STATE_OPEN, + self::CIRCUIT_STATE_HALF_OPEN, + self::CIRCUIT_STATE_DISABLED, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('url', $data ?? [], null); + $this->setIfExists('circuit_state', $data ?? [], null); + $this->setIfExists('reason', $data ?? [], null); + $this->setIfExists('fails', $data ?? [], null); + $this->setIfExists('trips', $data ?? [], null); + $this->setIfExists('disabled_at', $data ?? [], null); + $this->setIfExists('first_fail_at', $data ?? [], null); + $this->setIfExists('last_status', $data ?? [], null); + $this->setIfExists('next_probe_at', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['url'] === null) { + $invalidProperties[] = "'url' can't be null"; + } + if ($this->container['circuit_state'] === null) { + $invalidProperties[] = "'circuit_state' can't be null"; + } + $allowedValues = $this->getCircuitStateAllowableValues(); + if (!is_null($this->container['circuit_state']) && !in_array($this->container['circuit_state'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'circuit_state', must be one of '%s'", + $this->container['circuit_state'], + implode("', '", $allowedValues) + ); + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets url + * + * @return string + */ + public function getUrl() + { + return $this->container['url']; + } + + /** + * Sets url + * + * @param string $url This webhook URL (same string as in `webhookUrl`) + * + * @return self + */ + public function setUrl($url) + { + if (is_null($url)) { + throw new \InvalidArgumentException('non-nullable url cannot be null'); + } + $this->container['url'] = $url; + + return $this; + } + + /** + * Gets circuit_state + * + * @return string + */ + public function getCircuitState() + { + return $this->container['circuit_state']; + } + + /** + * Sets circuit_state + * + * @param string $circuit_state Delivery state for this URL only. `ok` delivering; `degraded` recent qualifying fails (still delivering, warning email on the 2nd fail); `open` skipping most deliveries pending a probe; `half_open` probe in flight (resume only on HTTP 2xx); `disabled` sticky auto-disabled until self-serve re-enable (`POST /webhook` / `POST /settings` with this URL) or a successful probe before sticky disable. + * + * @return self + */ + public function setCircuitState($circuit_state) + { + if (is_null($circuit_state)) { + throw new \InvalidArgumentException('non-nullable circuit_state cannot be null'); + } + $allowedValues = $this->getCircuitStateAllowableValues(); + if (!in_array($circuit_state, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'circuit_state', must be one of '%s'", + $circuit_state, + implode("', '", $allowedValues) + ) + ); + } + $this->container['circuit_state'] = $circuit_state; + + return $this; + } + + /** + * Gets reason + * + * @return string|null + */ + public function getReason() + { + return $this->container['reason']; + } + + /** + * Sets reason + * + * @param string|null $reason Last qualifying fail reason, e.g. `http_404` or `timeout_or_network` + * + * @return self + */ + public function setReason($reason) + { + if (is_null($reason)) { + throw new \InvalidArgumentException('non-nullable reason cannot be null'); + } + $this->container['reason'] = $reason; + + return $this; + } + + /** + * Gets fails + * + * @return int|null + */ + public function getFails() + { + return $this->container['fails']; + } + + /** + * Sets fails + * + * @param int|null $fails Diagnostic only — safe for clients to ignore. Number of qualifying delivery failures in the rolling ~10 minute breaker window (not lifetime, not \"how many times the customer retried\"). Prefer `circuitState` / `disabledWebhooks`. + * + * @return self + */ + public function setFails($fails) + { + if (is_null($fails)) { + throw new \InvalidArgumentException('non-nullable fails cannot be null'); + } + $this->container['fails'] = $fails; + + return $this; + } + + /** + * Gets trips + * + * @return int|null + */ + public function getTrips() + { + return $this->container['trips']; + } + + /** + * Sets trips + * + * @param int|null $trips Diagnostic only — safe for clients to ignore. How many recovery probes already failed for this URL. Probes run on a schedule (≈15 min → 1 h → 6 h); sticky disable after 3 failed probes. The 6 h step is the last probe, not a blind \"unblock in 6 hours\" timer. + * + * @return self + */ + public function setTrips($trips) + { + if (is_null($trips)) { + throw new \InvalidArgumentException('non-nullable trips cannot be null'); + } + $this->container['trips'] = $trips; + + return $this; + } + + /** + * Gets disabled_at + * + * @return int|null + */ + public function getDisabledAt() + { + return $this->container['disabled_at']; + } + + /** + * Sets disabled_at + * + * @param int|null $disabled_at Epoch milliseconds when this URL became sticky-disabled + * + * @return self + */ + public function setDisabledAt($disabled_at) + { + if (is_null($disabled_at)) { + throw new \InvalidArgumentException('non-nullable disabled_at cannot be null'); + } + $this->container['disabled_at'] = $disabled_at; + + return $this; + } + + /** + * Gets first_fail_at + * + * @return int|null + */ + public function getFirstFailAt() + { + return $this->container['first_fail_at']; + } + + /** + * Sets first_fail_at + * + * @param int|null $first_fail_at Diagnostic only. Epoch ms of the first fail in the current ~10 minute window (pairs with `fails`; not a customer-facing period). + * + * @return self + */ + public function setFirstFailAt($first_fail_at) + { + if (is_null($first_fail_at)) { + throw new \InvalidArgumentException('non-nullable first_fail_at cannot be null'); + } + $this->container['first_fail_at'] = $first_fail_at; + + return $this; + } + + /** + * Gets last_status + * + * @return int|null + */ + public function getLastStatus() + { + return $this->container['last_status']; + } + + /** + * Sets last_status + * + * @param int|null $last_status Last HTTP status from this URL, when known + * + * @return self + */ + public function setLastStatus($last_status) + { + if (is_null($last_status)) { + throw new \InvalidArgumentException('non-nullable last_status cannot be null'); + } + $this->container['last_status'] = $last_status; + + return $this; + } + + /** + * Gets next_probe_at + * + * @return int|null + */ + public function getNextProbeAt() + { + return $this->container['next_probe_at']; + } + + /** + * Sets next_probe_at + * + * @param int|null $next_probe_at Epoch milliseconds of the next half-open probe, if any + * + * @return self + */ + public function setNextProbeAt($next_probe_at) + { + if (is_null($next_probe_at)) { + throw new \InvalidArgumentException('non-nullable next_probe_at cannot be null'); + } + $this->container['next_probe_at'] = $next_probe_at; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/lib/ObjectSerializer.php b/lib/ObjectSerializer.php index 77d5f24..21ade8a 100644 --- a/lib/ObjectSerializer.php +++ b/lib/ObjectSerializer.php @@ -13,7 +13,7 @@ /** * 1MSG WhatsApp Business API (Public) * - * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** All requests require `token` query parameter with JWT or API key. **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ + * Public client API for sending messages, managing groups, flows, and templates. This is the API exposed to end customers via platform.1msg.io **Authentication:** Pass the channel token in **one** place. The API checks JSON body (`token`), then query string (`?token=`), then `Authorization: Bearer` header (preferred in production). **Documentation:** https://help.1msg.io/platform-1msg/getting-start/quick-start **API Docs:** https://docs.1msg.io/ * * The version of the OpenAPI document: 1.0.0 * Contact: support@1msg.io diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a35dcb8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,15 @@ +{ + "name": "1msg-sdk-php-quality", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "1msg-sdk-php-quality", + "version": "1.0.0", + "engines": { + "node": "24.x" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4a1b8aa --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "1msg-sdk-php-quality", + "version": "1.0.0", + "private": true, + "type": "module", + "engines": { + "node": "24.x" + }, + "packageManager": "npm@11.17.0", + "scripts": { + "format:check": "node tools/quality/node_modules/prettier/bin/prettier.cjs --check \"quality/**/*.mjs\" \"*.mjs\" package.json knip.json .github/workflows/quality.yml AGENTS.md", + "format": "node tools/quality/node_modules/prettier/bin/prettier.cjs --write \"quality/**/*.mjs\" \"*.mjs\" package.json knip.json .github/workflows/quality.yml AGENTS.md", + "lint:static": "node tools/quality/node_modules/eslint/bin/eslint.js . --max-warnings=0", + "quality:guards": "node tools/quality/run.mjs guards", + "lint:unused:production": "node tools/quality/run.mjs unused-production", + "lint:unused:full": "node tools/quality/run.mjs unused-full", + "lint:duplicates": "node tools/quality/run.mjs duplicates", + "lint:duplicates:full": "node tools/quality/run.mjs duplicates-full", + "test:quality": "node --test tools/quality/test/*.test.mjs", + "hooks:install": "node tools/quality/node_modules/husky/bin.js" + }, + "lint-staged": { + "*.{mjs,json,md,yml}": "node tools/quality/node_modules/prettier/bin/prettier.cjs --write" + } +} diff --git a/quality.config.mjs b/quality.config.mjs new file mode 100644 index 0000000..ed8ca14 --- /dev/null +++ b/quality.config.mjs @@ -0,0 +1,14 @@ +export default { + schemaVersion: 1, + profile: "D", + defaultBranch: "main", + sourceRoots: ["quality", "eslint.config.mjs", "quality.config.mjs"], + generated: [], + comments: { + full: ["quality/**/*.mjs"], + }, + duplicatePaths: ["quality"], + duplicateMaxPercentage: 0, + duplicateFullPaths: ["quality"], + baselineDir: "quality/baselines", +}; diff --git a/quality/contract-smoke.php b/quality/contract-smoke.php new file mode 100644 index 0000000..6b50690 --- /dev/null +++ b/quality/contract-smoke.php @@ -0,0 +1,40 @@ + 'Hello 👋', 'chat_id' => '12020721369@c.us']); +$payload = (array) ObjectSerializer::sanitizeForSerialization($model); +requireContract($payload['body'] === 'Hello 👋', 'required body changed'); +requireContract($payload['chatId'] === '12020721369@c.us', 'chatId wire name changed'); +requireContract(!array_key_exists('phone', $payload), 'unset phone serialized'); +requireContract(!array_key_exists('quotedMsgId', $payload), 'unset quotedMsgId serialized'); +requireContract(!(new SendMessageRequest())->valid(), 'missing required body accepted'); + +$history = []; +$mock = new MockHandler([new Response(200, ['Content-Type' => 'application/json'], '{"sent":true,"id":"fixture-message"}')]); +$handler = HandlerStack::create($mock); +$handler->push(Middleware::history($history)); +$api = new MessagingApi(new Client(['handler' => $handler])); +$response = $api->sendMessage('fixture+token&value', 'Hello 👋', null, '12020721369@c.us'); +requireContract($response->getSent() === true, 'response sent flag not decoded'); +requireContract(count($history) === 1, 'unexpected transport request count'); +$request = $history[0]['request']; +parse_str($request->getUri()->getQuery(), $query); +requireContract($query['token'] === 'fixture+token&value', 'token did not round-trip'); +requireContract(str_ends_with($request->getUri()->getPath(), '/sendMessage'), 'operation path changed'); +requireContract((string) $request->getBody() !== '', 'required request payload was lost'); +echo "offline SDK contract smoke passed\n"; diff --git a/quality/generator/generate.mjs b/quality/generator/generate.mjs new file mode 100644 index 0000000..d26a6a9 --- /dev/null +++ b/quality/generator/generate.mjs @@ -0,0 +1,145 @@ +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { + cp, + mkdir, + mkdtemp, + readFile, + readdir, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const directory = dirname(fileURLToPath(import.meta.url)); +const root = resolve(directory, "../.."); +const config = JSON.parse( + await readFile(join(directory, "provenance.json"), "utf8"), +); +/** Hash immutable producer inputs and complete generated bytes. */ +const hash = (bytes) => createHash("sha256").update(bytes).digest("hex"); +const spec = await readFile(join(directory, "openapi.yaml")); +if (hash(spec) !== config.contractSha256) + throw new Error("Pinned contract checksum mismatch"); +const cache = join(tmpdir(), "1msg-openapi-generator"); +await mkdir(cache, { recursive: true }); +const jar = join(cache, `${config.generatorVersion}.jar`); +try { + await readFile(jar); +} catch { + const response = await fetch( + `https://repo.maven.apache.org/maven2/org/openapitools/openapi-generator-cli/${config.generatorVersion}/openapi-generator-cli-${config.generatorVersion}.jar`, + ); + if (!response.ok) + throw new Error(`Generator download failed: ${response.status}`); + const bytes = Buffer.from(await response.arrayBuffer()); + if (hash(bytes) !== config.generatorSha256) + throw new Error("Generator checksum mismatch"); + await writeFile(jar, bytes); +} +if (hash(await readFile(jar)) !== config.generatorSha256) + throw new Error("Cached generator checksum mismatch"); +/** Recursively inventory only declared generated code, excluding hand-owned tests and package metadata. */ +async function inventory(directory, prefix = "") { + const result = {}; + for (const entry of (await readdir(directory, { withFileTypes: true })).sort( + (a, b) => a.name.localeCompare(b.name), + )) { + const path = prefix + entry.name; + // Descend only into ancestors or descendants of declared generated roots; unrelated authored files stay untouched. + if (entry.isDirectory()) { + // Match an ancestor or descendant of an explicitly declared generated directory. + if ( + config.roots.some( + (item) => + item.startsWith(path + "/") || + path.startsWith(item.replace(/\/$/, "") + "/") || + item.replace(/\/$/, "") === path, + ) + ) + Object.assign( + result, + await inventory(join(directory, entry.name), `${path}/`), + ); + } else { + // Match complete generated roots and top-level Go files while preserving handwritten contract tests. + if ( + config.roots.some((item) => + item.endsWith("/") + ? path.startsWith(item) + : item === "*.go" + ? !path.includes("/") && + path.endsWith(".go") && + !path.endsWith("_test.go") + : item === path, + ) + ) + result[path] = hash(await readFile(join(directory, entry.name))); + } + } + return result; +} +const temporary = await mkdtemp(join(tmpdir(), "1msg-native-generation-")); +try { + const properties = Object.entries(config.additionalProperties) + .map(([key, value]) => `${key}=${value}`) + .join(","); + const result = spawnSync( + "java", + [ + "-jar", + jar, + "generate", + "-i", + join(directory, "openapi.yaml"), + "-g", + config.generatorName, + "-o", + temporary, + `--additional-properties=${properties},hideGenerationTimestamp=true`, + "--global-property=apiTests=false,modelTests=false", + ], + { stdio: "inherit" }, + ); + if (result.status !== 0) + throw new Error(`Generator failed: ${result.status}`); + if (config.postprocess) { + const processResult = spawnSync( + process.execPath, + [join(root, "quality/postprocess.mjs"), temporary], + { stdio: "inherit" }, + ); + if (processResult.status !== 0) + throw new Error("Generated compatibility transform failed"); + } + const expected = await inventory(temporary); + if (!Object.keys(expected).length) + throw new Error("Generator produced no inventoried code"); + const inventoryPath = join(directory, "inventory.json"); + if (process.argv.includes("--write")) { + const previous = await inventory(root); + for (const path of Object.keys(previous)) + if (!(path in expected)) await rm(join(root, path)); + for (const path of Object.keys(expected)) { + await mkdir(dirname(join(root, path)), { recursive: true }); + await cp(join(temporary, path), join(root, path)); + } + await writeFile(inventoryPath, JSON.stringify(expected, null, 2) + "\n"); + } else { + for (const actual of [ + await inventory(root), + JSON.parse(await readFile(inventoryPath, "utf8")), + ]) + if (JSON.stringify(actual) !== JSON.stringify(expected)) + throw new Error( + "Generated drift: review producer then run node quality/generator/generate.mjs --write", + ); + } + console.log( + `Verified ${Object.keys(expected).length} generated ${config.language} code paths`, + ); +} finally { + await rm(temporary, { recursive: true, force: true }); +} diff --git a/quality/generator/inventory.json b/quality/generator/inventory.json new file mode 100644 index 0000000..6c51688 --- /dev/null +++ b/quality/generator/inventory.json @@ -0,0 +1,117 @@ +{ + "lib/Api/CallingApi.php": "e47e6d53db1d1624aa4f3cc84e8bf4240f67c4f3c8b6e5bc3757bd0d78305773", + "lib/Api/CatalogApi.php": "be364826bd3a4e1811e5d10d5043141467111377e99226d91071da55b96c49cc", + "lib/Api/ChannelApi.php": "df436997c5b994d1d157b685e0f850f81668b10e2dd2e909e773598ba63d3e02", + "lib/Api/FlowsApi.php": "6fe7bf2694e4777d298f6e198c8343c16bf70088ff1a66546da516655cb824f4", + "lib/Api/GroupsApi.php": "f1bc44a5ac64719554c3963190e98cac11f606cee2e645e928c3199c111f0f6a", + "lib/Api/MessagingApi.php": "8943c0e210d6383b8b4420d61b00e688ca8ba70f3b58022f2b775517461dc42f", + "lib/Api/ProfileApi.php": "c7b9a8776f416d8bce134d1c6dfdeb6b56f97754ac9a7ff25dea510e8fa46c29", + "lib/Api/TemplatesApi.php": "55f5d7e2a420886c02158f99c6b7c3f2a44dd30ecbdebfe1eaad82ada51a07d1", + "lib/Api/UsersApi.php": "3d5df6879c0f805f8fb34ef593f6749a3e76fe3b1f42bcb8b66d3a59e6f2eb26", + "lib/Api/WebhooksApi.php": "54558e1f231b73ee0c28b9716af982a2a62248141a67e860cfcc1e84ec7a9fd8", + "lib/ApiException.php": "8bfff8fdc9c5ef5bdb2310b1ab39ab368c33f16b8bfdd8e9e8a39f33a3f26c55", + "lib/Configuration.php": "5df9fac6b19b8db91a587a206665869aab082cc2403872b3b21b70cd4b6dc3a8", + "lib/FormDataProcessor.php": "58c694c0e241cbe1331e7b7f1f4350c300ed620dbebd32e0474b5f84ce527256", + "lib/HeaderSelector.php": "f8df3856c6fc1bdf6ccc6608387cf6851f0001cb5b872e8506add006fa449bdf", + "lib/Model/AddTemplate200Response.php": "426bdbb9a5759453597903ff23e18def0b9fee4a02c54ca75b55112ff00ded6d", + "lib/Model/AddTemplateRequest.php": "a5420a7de95874b2afdba916c8e358ebcf9cb3003b1df386e64082b538e3c2ad", + "lib/Model/BlockUserRequest.php": "c4d854c616b97e695a6ed76ed0fd836e8a206222af32fbad2688518f8c1db8b5", + "lib/Model/CallingSettings.php": "dad2c400eebd33e94a750bb1f12a266faf01e8b759db42a6b19af3e9994f2f48", + "lib/Model/CallingSettingsCalling.php": "c140b888355c49f5e22fcee31be8f36ce1f3a15fb79cdaf1793a46834ad07dbe", + "lib/Model/CallingSettingsCallingAudio.php": "a1cfbdecd82989c6a1b6272c099b7b02b81260c0e7d7761491e82622c90aa5de", + "lib/Model/CallingSettingsCallingCallHours.php": "43d0032886ec9437191ea95444f94c22829721431205a5859823d69b0ea3952a", + "lib/Model/CallingSettingsCallingSip.php": "46da31c7733f54b958342913d5210b830bc4795e429f8803d3550860dd58b25f", + "lib/Model/CallingSettingsCallingSipServersInner.php": "9ed2a658a616a43962653f7a4c06d9fc52831f91fba3b1a224d2097bca29c15c", + "lib/Model/CallingSettingsCallingVideo.php": "011baa0bce060d0bcc372953017bbdfa6762a5eb728dc752aff29d7b5e08357b", + "lib/Model/ConversationalAutomation.php": "705e8b1e54a3eea70d9ea8c8b1a467402914098968c5b89cd92b7c4c322e588e", + "lib/Model/ConversationalAutomationCommandsInner.php": "6fefc002c2179a76397bf6fe59ec41bda3ab4b003a634779ee0dd6e17940d7b1", + "lib/Model/CreateCommerce200Response.php": "fd626452cbb894faafe20d4168d47f8c16280634f27bc28e5839af42eb33a12d", + "lib/Model/CreateCommerceRequest.php": "2d1c3e34e77be620ba3bc96432d8d73c426fbc63c9dd066c1aa989d0009a5fe6", + "lib/Model/CreateCommerceRequestParams.php": "d9762f847334224caf42d611550af631f5b55fbdc1c8a30350c0ba72187c781b", + "lib/Model/CreateFlowsRequest.php": "dce765e534a16904622fb513b20295e4ae947727159c489b98d79d51793130c1", + "lib/Model/CreateGroups200Response.php": "7341bae35a28306323720d5ece87e34cd9c01ebfd70e9eab07a26b26aa12af60", + "lib/Model/CreateGroupsGroupIdRequest.php": "84e1bff205c4a82feb47d04f2fe505e5dbb1fc63676e54e6e2cda43c1e9ac444", + "lib/Model/CreateGroupsRequest.php": "5ffb590a02ce6c0f9d101d386dbb72d64a466fc11d3e0039f9218b2d9b8f80f6", + "lib/Model/CreateReadMessage200Response.php": "75306396c2568344f3afccc2ef0bf202870c2fc9a09cea93d94cc34b1be9b319", + "lib/Model/CreateReadMessage401Response.php": "9c8a40c8fa3e42dc39481335b7eff9253ec33d33f50d5d7e81fe4f0b687e2af4", + "lib/Model/CreateReadMessageRequest.php": "7790a016609a945587d54edc099f4909c9d83f7c51843599439098fc74043ef2", + "lib/Model/CreateSettingsRequest.php": "964e87492a1b597acc48c413a557601328e92a7f5e7255333245d286c93fe7fe", + "lib/Model/CreateSettingsRequestWebhookUrl.php": "ae91bbadc800af0d38058e53ace9f4f78f1cf41c07c6adc7369ed9f9b2f2d165", + "lib/Model/CreateUploadMedia200Response.php": "d7b5425e1d547d0b71d92452b93cf6ac81c22166f7f3cd7208cface01fdec6ff", + "lib/Model/CreateUploadMediaRequest.php": "b56ebcc53f9ba17c49523c9d27519dc3eff1f9585915e4c7c511ae52ecceb38e", + "lib/Model/DeleteFlowsFlowId200Response.php": "cd47b792cb9ce7fcc53e8bc0bfad2cce10a1ca801e4abce153fbc452586f4e8d", + "lib/Model/DeleteGroupsGroupId200Response.php": "2951b8f860d3a8120974bd6749c6bff26e67d4b1bd64b3b5f402d7477ee1b07c", + "lib/Model/DeleteMediaLegacyRequest.php": "e76b2f3e80ae3490ee9ab7cd6742729cb24709d63360e4c0b1dfb0580a2b2090", + "lib/Model/ErrorResponse.php": "2cc48e573c40f63603e5cea690fd145c8d428ce517b848724171991080b75bc0", + "lib/Model/GetCommerce200ResponseInner.php": "d8c50fc18fe910b98dd1998c2eaf6c1a90b96ddf094bb564442d863d1605f06e", + "lib/Model/GetMmLiteStatus200Response.php": "2262130f6708a2c70f63d6e61e1d6258b8776afd49679022e14b2213152d6bd6", + "lib/Model/GetStatus200Response.php": "ab7e72c5aebf80e27ec2859f7549d40a9bb0cc8583692ebe4d7e378683c29295", + "lib/Model/GetWebhook200Response.php": "4f0cf52dd04153f0ba96c060f0b8735483dac186dafc39c240f855bac4afe639", + "lib/Model/GetWebhook200ResponseWebhookUrl.php": "1509163e92cd4cf3941d5a937e16bf06d601bcf2fba472a1432952ec15dfbdd9", + "lib/Model/GetWhatsappBusinessEncryption200Response.php": "76cf84dae5239d31b027cc0fedd0989ca1c3c0acbc5ebd221c2062c1f8adaa9d", + "lib/Model/InitiateCallRequest.php": "6d54d4b984fba3c37d68b57127ff41e1ff1e1f2f7ba686ddc4e185c78f53db5a", + "lib/Model/InitiateCallRequestSession.php": "e2602adc43b3a1c596be89441c4f35b1c9a64f290889e7ed8ea967b512adc64f", + "lib/Model/InitiateCallResponse.php": "835e105e2d16e7057bec2933443957b67eaebf3bc6642d04d8f05bb0e846c7b4", + "lib/Model/InitiateCallResponseCallsInner.php": "9281cad4a05c67354182545199dca8fa48bf01531d63b4c0cdbc2765e4520ed5", + "lib/Model/InitiateCallResponseResponse.php": "6eb8e04bbbcfc24a6ac657102d676c16a88b4366efc64b068140b9c448f3c18a", + "lib/Model/ListBlockedUsers200Response.php": "49eb147888d983a98407015d1251f76c6b90af6ba2cadffc0644406349be255f", + "lib/Model/ListFlows200Response.php": "ad8e4644324b44b339195fd99ad899cd5762010393b90012805166ecb087ac30", + "lib/Model/ListMessages200Response.php": "b2a6341f19edc274f79b131ef8ac1d66e2d70d1b685eda4867d646c0fa68ad39", + "lib/Model/ListSettings200Response.php": "2def6d8a3d84892198436d9d505a871809bf8b47fc28ce21ba0bace09249c928", + "lib/Model/ListSettings200ResponseWebhookUrl.php": "19c825ca1bec7c4ea5663f750314a1dae13e3b6c14b6ce503083278ab0609702", + "lib/Model/ListTemplates200Response.php": "6fd4cd72744bf3a22db9cbdb980d9ddadfd3bb9976463ec7e09376a7fd346a62", + "lib/Model/MessageSentResponse.php": "9acaa1b4ac6a7762a45a7bc8f5a1fa4ec9c4c3a2d4996b286b0c6a8d18137dd5", + "lib/Model/ModelInterface.php": "417e01de8223a459121383284a9e1ef463d4be795604b8ce4a147419bd176229", + "lib/Model/PatchFlowsFlowIdAssets200Response.php": "a840822cc9d1c289ac2ba7818bd5dd0843d8191fa8d891b118ff1b4a7f27d04a", + "lib/Model/PatchFlowsFlowIdAssetsRequest.php": "c049b85ec5388eb341d9ccf93ad279c7de201d7ca1d0394b37c2866c763778b6", + "lib/Model/PatchFlowsFlowIdMetadataRequest.php": "35d24967ee3e3191062046fa48b5dd93f1f14af938425f6c20c223ef95f89acf", + "lib/Model/ProfileInfo.php": "c1be391ca45984446e89555912df5b3adf9686c21704891079456044db59fb4f", + "lib/Model/RemoveTemplateRequest.php": "bbd55d8ac4d9f5c8ebd96b78022c95f30e2994e5aacbaff2b529ff4662ab5096", + "lib/Model/RetrieveMedia200Response.php": "73ed63aae1762e67f773bfdff49b614b0e54175f6e230fa9dd86d0712f534018", + "lib/Model/SendAddressMessageRequest.php": "ae038614acd5a7488e9079db649453d231667b67e3a8b3306fa24a9b4bd8091e", + "lib/Model/SendButtonRequest.php": "a61ca4c4ff5cc16b9e61b5f8c730f82cba1d6991e0c8be3b754d7b5168a5f133", + "lib/Model/SendButtonRequestSectionsInner.php": "d093f9b77d1d1dbcb41a6cae27fc85f21ce295cb30fb98fe61e16de8e61647e9", + "lib/Model/SendButtonRequestSectionsInnerReply.php": "d01b904848b516b4608862df7d17a766e7c51e7cefa17360eab3b73d3ef9d0ab", + "lib/Model/SendCarouselRequest1.php": "05d9bb3b3c8b7ba18fee28e66133e8e93a0afbed94bcf0617f0f10619bc7495d", + "lib/Model/SendContactRequest.php": "e086db80920dfd2d525d053cdc7abacb9c989378cf1ae46f36f5350084173f75", + "lib/Model/SendContactRequestContact.php": "630193d8c2b26cfb7a17541af5e1e6734aefbad30060923713b959d11a35b347", + "lib/Model/SendContactRequestContactName.php": "18360e766be316abeda04cd49ca07b828129f753a69caaa97a1e12531cd0d41e", + "lib/Model/SendContactRequestContactsInner.php": "9a9a3c75110ec2b2cd08332226f113cdffe20fda456e14d529859b164e569419", + "lib/Model/SendContactRequestContactsInnerName.php": "2a08eb21d27f82df78624d25f31ed77a44678446ca413b33a3ec9329c5135d6d", + "lib/Model/SendContactRequestContactsInnerOrg.php": "33144c02f014a2d99c2521cce3413b86bc5ed5824208b1e74f82fac904965cdb", + "lib/Model/SendContactRequestContactsInnerPhonesInner.php": "3dd5edd0ee84762a89c16b5595bdc4e48e400aa69b1298628d70a07d6c1b6098", + "lib/Model/SendCtaUrlRequest.php": "da60aa647a11180ef9a520e7a30f6e44d8e5d00b4e92234864a925d71a8c10f6", + "lib/Model/SendFileRequest.php": "5e7cf64cba16104f7e0678d54461d5828702f15c67ebae4016009d14c9db8f7e", + "lib/Model/SendFlowRequest1.php": "244f499362065c2cb932ce4d98557703399aad3f09d345690d8bb5be6923f17a", + "lib/Model/SendFlowRequest1Header.php": "131c1d038d9df19f5debed86452f2516586b8d0a83628418a7fed3cf4e7badbc", + "lib/Model/SendFlowRequestHeader.php": "b866cca9050cd73ef3b354f6a8cfc05a4685f6fbbc77a24f9c63db529cafc1f7", + "lib/Model/SendListRequest.php": "244703f3cc9fb1705aa9d2bf2d50b7bdec9357613c250bc26f3dc4731dd09b02", + "lib/Model/SendListRequestSectionsInner.php": "f5d956aa0a8fa60d746e700b198e01e4eb704bdbda63358edd4509dec1abdbf9", + "lib/Model/SendListRequestSectionsInnerRowsInner.php": "ba941594b98506fd488a3deac1f87598a4acd3b4079ee887914bfb74f8820e31", + "lib/Model/SendLocationRequest1.php": "1fe0961921fca3f19979ad5de7e81a1208a4a0d9c87b9c1e837398aa9d5b9e7d", + "lib/Model/SendLocationRequestRequest.php": "0b83736707e0c7d6f29bf759acfb5d5fe1cf49faf07cdcc230a39c7e0701af1f", + "lib/Model/SendMessageRequest.php": "607195c5df93ab986266b9710972b65d4f2a2b6b2731c468b4ce51e99e11489a", + "lib/Model/SendOrderDetailsRequest.php": "0188b04437f6b357666a2df89480e34f608411fdb7dd610865eb3dca1a365615", + "lib/Model/SendOrderDetailsRequestLanguage.php": "3be5407c48ebb7ebc5740758de413a78e1e53c90b3b2be21df267d32449763c6", + "lib/Model/SendOrderDetailsRequestOrder.php": "14c0f082683c90ef83f0305a62d6255e697720189f4b6f29c78c7b54cfec9ba2", + "lib/Model/SendOrderDetailsRequestOrderItemsInner.php": "970b971da7bcfe2fc06a2bf9dd59b3df516717b51033bdeb462cfa825a902e00", + "lib/Model/SendOrderDetailsRequestOrderItemsInnerAmount.php": "ffaf77101eda5960f56d5b64c1516e6e255834be6b118a7c6169fd093d7e66b5", + "lib/Model/SendPaymentRequestRequest.php": "2442b5f3b71c966892dd5805670729f8588d1d642c9afe87ec918839fa77388b", + "lib/Model/SendProductRequest.php": "a80cc7e615f8194cdffda06aca39d2da5876b932d7c987f4d59642a38adb2266", + "lib/Model/SendReactionRequest.php": "5f206e00c80f2438d39e38e4f5ed3623b4c7c4116577ea5c7bda022024e78e6b", + "lib/Model/SendStickerRequest.php": "e4f09b887933e44f7d7e633aca4c37e606d3452a04b43de455683056e5ce2888", + "lib/Model/SendTemplateRequest.php": "0d65de2791c4c58821da26011ca949285fbd9d3676a6857f644027925685cc8e", + "lib/Model/SendTemplateRequestLanguage.php": "b2434c5ad5de56f8c0cf62697667873642e615f2404e235c5afa9fd4a79269b1", + "lib/Model/SetWebhook200Response.php": "c3291e5d6ee034f4f7683ca8ac9e707f2a1a95b467d2427b0e1f2c393d75999b", + "lib/Model/SetWebhook200ResponseWebhookUrl.php": "5d9ee95a5dc1946766a09802d4dddd5e8878ae4b78bb0039fcca0b6ebd0561a3", + "lib/Model/SetWebhookRequest.php": "533ea504ab250eb7d698c83c7c157cdd60dec0009ae02f53d2f7ab0a68c6c0b8", + "lib/Model/SetWebhookRequestWebhookUrl.php": "73f73fd8a25c01b3fc7db1274754976735c6d183c5dbbff56540a85080ac092a", + "lib/Model/SetWhatsappBusinessEncryptionRequest.php": "49b8d485867073efb9d1d4279900e85f3cafa8e621a898a5752b7ee41d183408", + "lib/Model/SuccessResponse.php": "100d5e40af92d3b09387c15b2ea7804b203a258f9e5887b57cb11ffe0a8148a8", + "lib/Model/UpdateCallingSettings200Response.php": "b83b64f3ba316e62173127eb6ea04476d4909dba923b690cd7b61f0e74bdf5a5", + "lib/Model/UpdateCallingSettings200ResponseResponse.php": "dedb8c557c9af0f8b791d48fc7451ef74788ea562cd8faa06e8fa93bd33dbf99", + "lib/Model/UpdateMe200Response.php": "2537c9a2b097152b9213d64f4f6c5ddb91ae5e328dceaec0ae74a93e18b0a51e", + "lib/Model/UpdateMeRequest.php": "94be8bf3bed6d6ba383b23aac21047ee515b821088749617c500bec03a809227", + "lib/Model/WebhookUrlStatus.php": "bef08dfba4931d5d4d4f8d99f110090e9e8594f76ef2a65ba9ca4de54711e217", + "lib/ObjectSerializer.php": "e16410b0aba0cad03c3d0438cfcc7477f71c357229655941833432418cb1dd98" +} diff --git a/quality/generator/openapi.yaml b/quality/generator/openapi.yaml new file mode 100644 index 0000000..ebed642 --- /dev/null +++ b/quality/generator/openapi.yaml @@ -0,0 +1,4621 @@ +openapi: 3.0.3 +info: + title: 1MSG WhatsApp Business API (Public) + description: > + Public client API for sending messages, managing groups, flows, and + templates. + + + This is the API exposed to end customers via platform.1msg.io + + + **Authentication:** Pass the channel token in **one** place. The API checks + + JSON body (`token`), then query string (`?token=`), then + + `Authorization: Bearer` header (preferred in production). + + + **Documentation:** + https://help.1msg.io/platform-1msg/getting-start/quick-start + + **API Docs:** https://docs.1msg.io/ + version: 1.0.0 + contact: + name: 1MSG API Support + url: https://help.1msg.io + email: support@1msg.io +servers: + - url: https://api.1msg.io + description: Production + - url: https://api-stage.1msg.io + description: Staging + - url: http://127.0.0.1:3050 + description: Local development +tags: + - name: Messaging + description: Send messages, files, templates, and other content + - name: Users + description: Block, unblock, and list blocked WhatsApp users + - name: Profile + description: Channel profile information (read and update) + - name: Channel + description: Channel status and automation settings + - name: Catalog + description: Catalog and cart commerce settings (`GET`/`POST /commerce`) + - name: Groups + description: Create and manage WhatsApp groups + - name: Flows + description: WhatsApp Flows (interactive forms) + - name: Templates + description: Get and manage message templates + - name: Webhooks + description: > + Client webhook URL configuration (`GET`/`POST /webhook`). + + + Auto-disable is **per URL**, not per channel. Prefer `webhookStatuses` on + + `GET /webhook` / `GET /settings`. Top-level `haveDisabledWebhooks` means + at + + least one configured URL is sticky-disabled; live URLs still receive + events. + - name: Calling + description: > + WhatsApp Calling API (beta) — VoIP **signaling** proxy. + + + 1msg does **not** host media, store call history, or store recordings. + + You need your own WebRTC or SIP stack. + + + ### Prerequisites + + - Meta Calling enabled on the number (Cloud API; not COEX) + + - Paid channel — trial / `subscriptionBlocked` → **403** plain text + + - Webhook URL configured (`POST /webhook`) for `calls` events + + + ### Inbound (user → business) + + 1. Webhook `type=calls`, `event=connect` — take `call_id` + SDP **offer** + + 2. Optional: `POST /initiateCall` `action=pre_accept` + WebRTC **answer** + SDP + + 3. `action=accept` + WebRTC **answer** SDP, or `action=reject` + + 4. Later `event=terminate`, or `action=terminate` to hang up + + + Answer within ~**30–60s** or Meta ends as unanswered. + + + **Critical:** `accept` / `pre_accept` need a **WebRTC-generated SDP + answer**. + + Do **not** echo Meta's offer. Postman/curl alone cannot establish real + media. + + + ### Outbound (business → user) + + 1. Send Call Permission Request (CPR) — template or interactive + + 2. Wait for permission reply on the **messages** webhook + (`call_permission_reply`) + + 3. `POST /initiateCall` `action=connect` + business SDP **offer** + + 4. Apply Meta's SDP **answer** from the `calls` connect webhook + + + Meta limits (summary): permission ~7 days; up to 5 calls / 24h / user; + + max 2 CPR / 7 days; auto-revoke after 4 unanswered. + + + ### Webhooks (`type=calls`) + + Passthrough of Meta `field=calls` plus `type`, `instanceId`. + + Events: connect (offer or answer SDP), statuses (`RINGING` / `ACCEPTED` / + + `REJECTED`), terminate. CPR replies use the messages path, not + `type=calls`. + + + See operations below for request bodies and examples. +paths: + /me: + get: + summary: Get Profile Info + description: > + Return the WhatsApp Business profile for this channel: About, address, + + description, email, phone, websites, industry (`vertical`), and profile + photo URL. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Profile information + content: + application/json: + schema: + $ref: "#/components/schemas/ProfileInfo" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Profile + operationId: getMe + post: + summary: Update profile info + description: > + Update WhatsApp Business Account profile fields. At least one of + + about, description, email, photo, address, vertical, websites is + required. + + Blocked when the channel subscription limit is exceeded. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + about: + type: string + description: Profile About section (WhatsApp status text) + example: Available 9–18 + address: + type: string + description: Business address shown on the profile + description: + type: string + description: Business description (max 256 characters) + email: + type: string + description: Business email shown on the profile + example: support@example.com + vertical: + type: string + description: Industry of the business + photo: + type: string + description: HTTPS URL or data:image/...;base64,... payload + websites: + type: array + items: + type: string + description: Business website URLs + additionalProperties: true + example: + about: Available 9–18 + email: support@example.com + responses: + "200": + description: Profile updated + content: + application/json: + schema: + type: object + properties: + result: + type: string + example: success + additionalProperties: true + example: + result: success + "401": + $ref: "#/components/responses/InvalidToken" + "422": + description: Wrong request (no updatable fields) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Profile + operationId: updateMe + /sendMessage: + post: + summary: Send a Message + description: > + Send a message to an existing chat. (Only if the dialogue has an Open + Session). + + The message will be added to the queue for sending and delivered even if + the phone + + is disconnected from the Internet or authorization is not passed. + + + Only one of two parameters is needed to determine the destination - + chatId or phone. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/SendMessageRequest" + application/json: + schema: + $ref: "#/components/schemas/SendMessageRequest" + example: + body: Hello World + phone: 12020721369 + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendMessage + /sendFile: + post: + summary: Send a File + description: > + Send a file to an existing chat. (Only if the dialogue has an Open + Session). + + Only one of two parameters is needed to determine the destination - + chatId or phone. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/SendFileRequest" + application/json: + schema: + $ref: "#/components/schemas/SendFileRequest" + example: + phone: 12020721369 + body: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf + filename: dummy.pdf + caption: Here is the file + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendFile + /sendLocation: + post: + summary: Send a Location + description: > + Send a location to an existing chat. (Only if the dialogue has an Open + Session). + + Only one of two parameters is needed to determine the destination - + chatId or phone. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - lat + - lng + properties: + lat: + type: string + description: "Latitude of the location. Example: 45.018337" + lng: + type: string + description: "Longitude of the location. Example: -73.968285" + address: + type: string + description: "Address of the location. Only displayed if name is present. + Example: 9766 Valley View St., New York, NY 10024" + name: + type: string + description: "Name of the location. Example: Facebook HQ" + quotedMsgId: + type: string + description: Quoted message ID (Cloud API) + chatId: + type: string + description: "Required if phone is not set. Chat ID from the message list. + Examples: 12020721369@c.us or + 120363046942338209@g.us(group)" + phone: + type: integer + description: "Required if chatId is not set. A phone number starting with the + country code. USA example: 12020721369." + application/json: + schema: + type: object + required: + - lat + - lng + properties: + lat: + type: string + lng: + type: string + address: + type: string + name: + type: string + quotedMsgId: + type: string + chatId: + type: string + phone: + type: integer + example: + lat: "45.018337" + lng: "-73.968285" + name: Facebook HQ + address: 9766 Valley View St., New York, NY 10024 + phone: 12020721369 + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendLocation + /sendContact: + post: + summary: Send a Contact + description: > + Send a WhatsApp contact card. + + + Preferred simple body (legacy-compatible): + + + ```json + + { + "phone": "79001234567", + "contact": { "name": "Lida", "phone": "+79181976551" } + } + + ``` + + + Cloud API shape also works: `contacts` is an array of objects with + + `name.formatted_name` and `phones[].phone` (E.164 string, **not** a + timestamp). + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + description: Recipient phone with country code. Use phone or chatId. + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + quotedMsgId: + type: string + contact: + type: object + description: Simple contact card. Use this or `contacts`. + required: + - name + - phone + properties: + name: + description: Display name (string) or Cloud API name object + oneOf: + - type: string + - type: object + additionalProperties: true + phone: + type: string + description: Contact phone in E.164 (`+79181976551`), not a timestamp + email: + type: string + org: + type: object + additionalProperties: true + url: + type: string + contacts: + type: array + items: + type: object + properties: + name: + type: object + properties: + formatted_name: + type: string + first_name: + type: string + last_name: + type: string + middle_name: + type: string + suffix: + type: string + prefix: + type: string + birthday: + type: string + addresses: + type: array + items: + type: object + additionalProperties: true + emails: + type: array + items: + type: object + additionalProperties: true + org: + type: object + properties: + company: + type: string + department: + type: string + title: + type: string + phones: + type: array + items: + type: object + properties: + phone: + type: string + description: E.164 phone (`+79181976551`). Not a timestamp. + type: + type: string + wa_id: + type: string + urls: + type: array + items: + type: object + additionalProperties: true + example: + phone: "{{testPhone}}" + contact: + name: John Doe + phone: "+1234567890" + operationId: sendContact + /sendList: + post: + summary: Send List Message + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + description: Recipient phone with country code. Use phone or chatId. + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + quotedMsgId: + type: string + body: + type: string + buttonText: + type: string + action: + type: string + sections: + type: array + items: + type: object + properties: + title: + type: string + rows: + type: array + items: + type: object + properties: + id: + type: string + format: utc-millisec + title: + type: string + description: + type: string + example: + phone: "{{testPhone}}" + body: This is a list message + buttonText: Click me + action: Action + sections: + - title: Section 1 + rows: + - id: "1" + title: Row 1 + description: Description for Row 1 + operationId: sendList + /sendButton: + post: + summary: Send Reply Buttons Message + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + description: Recipient phone with country code. Use phone or chatId. + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + quotedMsgId: + type: string + description: Optional Cloud API wamid to quote + body: + type: string + footer: + type: string + header: + type: object + additionalProperties: true + description: Optional interactive header (text/image/video/document) + buttons: + type: array + items: + type: object + description: Legacy alias for sections (`[{id,title}]`) + sections: + type: array + items: + type: object + properties: + type: + type: string + reply: + type: object + properties: + id: + type: string + format: utc-millisec + title: + type: string + example: + phone: "{{testPhone}}" + body: This is a buttons message + footer: Footer + sections: + - type: reply + reply: + id: "1" + title: Button 1 + operationId: sendButton + /sendLocationRequest: + post: + summary: Send Location Request Message + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + description: Recipient phone with country code. Use phone or chatId. + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + quotedMsgId: + type: string + body: + type: string + example: + phone: "{{testPhone}}" + body: Text + operationId: sendLocationRequest + /sendFlow: + post: + summary: Send WhatsApp Flow Message + description: > + Send Interactive WhatsApp Flow message to an existing chat. (Only if the + dialogue has an Open Session). + + Only one of two parameters is needed to determine the destination - + chatId or phone. + + + Use this method to send a published WhatsApp Flow as a service + (interactive) message. + + If the 24-hour window is closed, send a template with a FLOW button via + /sendTemplate. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - body + - flowId + - flowToken + - flowCta + properties: + body: + type: string + description: Flow message body text + header: + oneOf: + - type: string + - type: object + description: Flow header (string or text header object) + footer: + type: string + description: Footer text + flowId: + type: string + description: Published Flow ID + flowToken: + type: string + description: Flow token generated by the business + flowCta: + type: string + description: CTA button text + flowAction: + type: string + enum: + - navigate + - data_exchange + description: Flow action type + flowActionPayload: + type: object + description: Required for flowAction=navigate (screen is required). Ignored for + data_exchange. If data is provided, it must be a non-empty + object. + flowMessageVersion: + type: string + description: Flow message version (default "3") + mode: + type: string + enum: + - draft + - published + description: Flow mode (draft or published). If omitted, provider default + applies + flowActionData: + type: object + description: Shortcut for flowActionPayload.data (optional) + flowActionScreen: + type: string + description: Shortcut for flowActionPayload.screen (optional) + quotedMsgId: + type: string + description: Quoted message ID (Cloud API) + chatId: + type: string + description: "Required if phone is not set. Chat ID from the message list. + Examples: 12020721369@c.us or + 120363046942338209@g.us(group)" + phone: + type: integer + description: "Required if chatId is not set. A phone number starting with the + country code. USA example: 12020721369." + application/json: + schema: + type: object + required: + - body + - flowId + - flowToken + - flowCta + properties: + body: + type: string + header: + oneOf: + - type: string + - type: object + footer: + type: string + flowId: + type: string + flowToken: + type: string + flowCta: + type: string + flowAction: + type: string + enum: + - navigate + - data_exchange + flowActionPayload: + type: object + flowMessageVersion: + type: string + mode: + type: string + enum: + - draft + - published + flowActionData: + type: object + flowActionScreen: + type: string + quotedMsgId: + type: string + chatId: + type: string + phone: + type: integer + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendFlow + /sendReaction: + post: + summary: Send Reaction + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + description: Recipient phone with country code. Use phone or chatId. + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + body: + type: string + quotedMsgId: + type: string + example: + phone: "{{testPhone}}" + body: 😀 + quotedMsgId: "{{lastMessageId}}" + operationId: sendReaction + /sendCarousel: + post: + summary: Send Carousel + description: > + You can send product cards via Carousel in two ways: + + + Template messages: do not require a 24-hour customer service window + between you and the recipient. Use sendTemplate. + + + Free-form messages: can be sent only when a customer service window is + open between you and the recipient. Use sendCarousel. + + + The message structure in /sendCarousel is largely similar to sending a + template. However, in this case you must + + explicitly specify all elements that are created in advance when working + with templates. This is because the message + + is sent without using a template. + + + In /sendCarousel, for a Catalog Carousel there can be either 1 URL + button or one or more quick reply buttons. + + + Prefer the simple `cards` array (2–10 cards). Each card needs a header + media/product + + plus a CTA URL or quick-reply buttons. Example: + + + ```json + + { + "phone": "79001234567", + "body": "Pick a product", + "cards": [ + { + "header": { "type": "image", "image": { "link": "https://example.com/1.jpg" } }, + "body": { "text": "Card one" }, + "type": "cta_url", + "action": { "name": "cta_url", "parameters": { "url": "https://example.com/p1", "display_text": "Open" } } + }, + { + "header": { "type": "image", "image": { "link": "https://example.com/2.jpg" } }, + "body": { "text": "Card two" }, + "type": "cta_url", + "action": { "name": "cta_url", "parameters": { "url": "https://example.com/p2", "display_text": "Open" } } + } + ] + } + + ``` + + + `params` with a `CAROUSEL` component is the template-like alternative. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + body: + type: string + description: Text shown above the carousel. Optional. If omitted and params + include a body component, the body will be taken from + params. + cards: + type: array + minItems: 2 + maxItems: 10 + description: Preferred. 2–10 carousel cards (header + body + CTA or quick + replies). + items: + type: object + additionalProperties: true + params: + type: array + items: + type: object + description: > + Required. Template-like structure (same as sendTemplate + params). Must include a CAROUSEL component and its cards. + + + Structure: + + - params[] item with type CAROUSEL and cards (2..10) + + - cards[] item: + - components[] items: + - header: parameters[0] with type image/video/product + - image: {"image": {"link": "https://..."}} + - video: {"video": {"link": "https://..."}} + - product: {"product": {"catalog_id": "...", "product_retailer_id": "..."}} + - button: sub_type url or quick_reply + - url: parameters {"text": "Button", "url": "https://..."} + - quick_reply: parameters[] of {"id": "...", "text": "..."} + quotedMsgId: + type: string + description: Quoted message ID (Cloud API) + chatId: + type: string + description: "Required if phone is not set. Chat ID from the message list. + Examples: 12020721369@c.us or + 120363046942338209@g.us(group)" + phone: + type: integer + description: "Required if chatId is not set. A phone number starting with the + country code. USA example: 12020721369." + application/json: + schema: + type: object + properties: + body: + type: string + description: Text shown above the carousel + cards: + type: array + minItems: 2 + maxItems: 10 + description: Preferred. 2–10 carousel cards. + items: + type: object + additionalProperties: true + params: + type: array + items: + type: object + description: Template-like alternative — include a CAROUSEL component with cards + quotedMsgId: + type: string + chatId: + type: string + phone: + type: integer + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendCarousel + /sendProduct: + post: + summary: Send a Product + description: > + Send a single catalog product or a product list. + + Destination is `chatId` or `phone`. `action` is required: + + - single product: `{ "catalog_id": "...", "product_retailer_id": "..." + }` + + - product list: `{ "catalog_id": "...", "sections": [{ "title": "...", + "product_items": [{ "product_retailer_id": "..." }] }] }` + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - action + properties: + phone: + type: string + description: Recipient phone with country code. Use phone or chatId. + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + quotedMsgId: + type: string + body: + type: string + description: Optional message text shown with the product + footer: + type: string + header: + type: string + description: Text header (product list only) + action: + type: object + additionalProperties: true + description: Catalog action (`catalog_id` + `product_retailer_id`, or `sections` + for a list) + additionalProperties: true + example: + chatId: 12020721369@c.us + body: Have a look + action: + catalog_id: "448008021021719" + product_retailer_id: "0001" + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendProduct + /sendTemplate: + post: + summary: Send Template Message + description: > + Send a WhatsApp **template** message (works outside the 24-hour session + window). + + + Use this tool — not `send_message` — when the 24h session is closed. + + + `params` MUST be Cloud API **component objects**, never a flat list of + strings + + and never the old On-Prem `hsm` / `localizable_params` format. + + + Example body variable `{{1}}`: + + + ```json + + { + "type": "body", + "parameters": [{ "type": "text", "text": "Ivan" }] + } + + ``` + + + Do not send `"type": "hsm"` and do not send `params: ["Ivan", "123"]`. + + + Supported `params` button `sub_type` values include: `url`, + `quick_reply`, + + `copy_code` / `coupon_code`, `catalog`, `flow`, `limited_time_offer`, + and + + **`order_details`** (WhatsApp Payments **India only** — requires an + approved + + Utility template with an ORDER_DETAILS button). + + + Convenience wrapper with structured fields: `POST /sendOrderDetails`. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Templates + requestBody: + content: + application/json: + schema: + type: object + properties: + template: + type: string + description: Approved template name (template `name`, not the old HSM + `element_name`) + language: + type: object + description: Template language. Always set `policy` to `deterministic`. + properties: + policy: + type: string + description: Must be `deterministic` + example: deterministic + default: deterministic + code: + type: string + example: en + namespace: + type: string + description: Template namespace from `list_templates` + params: + type: array + description: > + Template components in WhatsApp Cloud API format. + + Each item is an object with `type` (`body`, `header`, + `button`) + + and `parameters` (objects with `type` + `text` / media / + action). + + Never a list of strings. Never HSM `localizable_params`. + items: + type: object + additionalProperties: true + description: | + Component object, e.g. {"type":"body","parameters":[{"type":"text","text":"Ivan"}]} or a header/button/carousel component. Not a string. + phone: + type: string + description: Recipient phone with country code (use phone or chatId) + chatId: + type: string + description: Recipient chat ID (`phone@c.us`, group `@g.us`, or BSUID `@lid`). + Use phone or chatId. + quotedMsgId: + type: string + description: Optional Cloud API wamid to quote + useMMlite: + type: boolean + description: > + Force Marketing Messages API (POST marketing_messages). + + If omitted: auto for MARKETING category when channel has + mm_lite_enabled + + and mm_lite_available service settings. + messageActivitySharing: + type: boolean + description: > + Sets message_activity_sharing on WABA payload. Enables click + tracking webhooks. + + Requires MM Lite path. Ignored on Cloud API fallback. + messageSendTtlSeconds: + type: integer + description: > + Template message TTL in seconds (message_send_ttl_seconds). + + MARKETING via MM Lite: 43200–2592000. AUTHENTICATION: + 30–900. + + UTILITY: 30–43200. AUTH/UTILITY also accept -1 (30-day + custom TTL). + example: + template: test_buttons_2 + language: + policy: deterministic + code: ru + namespace: 41123797_e45e_4f06_a2fa_443b27c6f5ec + phone: "{{testPhone}}" + params: + - type: body + parameters: + - type: text + text: Ivan + operationId: sendTemplate + /messages: + get: + summary: Get messages list + description: | + List stored chat history for this channel (requires history storage). + Filter with `chatId`, `limit`, `last`, message numbers, or time bounds. + parameters: + - $ref: "#/components/parameters/token" + - name: chatId + in: query + schema: + type: string + description: Filter messages for one chat (`phone@c.us`, group `@g.us`, or BSUID + `@lid`) + - name: limit + in: query + schema: + type: integer + description: Max rows to return (default 100) + - name: last + in: query + schema: + type: boolean + description: If true, return the latest messages (DESC). Default false. + - name: lastMessageNumber + in: query + schema: + type: integer + - name: firstMessageNumber + in: query + schema: + type: integer + - name: min_time + in: query + schema: + type: integer + description: Unix timestamp lower bound + - name: max_time + in: query + schema: + type: integer + description: Unix timestamp upper bound + - name: msgId + in: query + schema: + type: string + description: Filter by stored message id + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + notice: + type: string + messages: + type: array + items: + type: object + additionalProperties: true + additionalProperties: true + examples: + ok: + value: + messages: [] + historyDisabled: + value: + notice: saving history disabled + tags: + - Messaging + operationId: listMessages + /readMessage: + post: + summary: Mark message as read + parameters: + - $ref: "#/components/parameters/token" + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + messageId: + type: string + msgId: + type: string + typingIndicator: + type: boolean + default: false + description: Show WhatsApp typing indicator (max 25s or until reply) + application/json: + schema: + type: object + properties: + messageId: + type: string + msgId: + type: string + typingIndicator: + type: boolean + default: false + description: Show WhatsApp typing indicator (max 25s or until reply) + responses: + "200": + description: Server response example + content: + application/json: + schema: + type: object + properties: + result: + type: string + additionalProperties: true + example: + result: success + "401": + description: Invalid token + content: + application/json: + schema: + type: object + properties: + error: + type: string + tags: + - Messaging + operationId: createReadMessage + /uploadMedia: + post: + summary: Upload media + description: > + Upload a file to WhatsApp media storage and get a numeric `mediaId` + + for later use with `send_file` / `send_sticker` (`mediaId` field, not + `body`). + + + Pass the file as `body` **or** `url` — an HTTP(S) URL or a base64 data + URI. + + The `mediaId` in the response is a numeric string (keep it as a string). + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + body: + type: string + description: File HTTP(S) URL or base64 data URI (`data:;base64,...`) + url: + type: string + description: Alias for `body` — public HTTP(S) URL of the file to upload + example: + body: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + mediaId: + type: string + additionalProperties: true + tags: + - Messaging + operationId: createUploadMedia + /sendSticker: + post: + summary: Send sticker message + description: Send a WhatsApp sticker by mediaId or link URL. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + phone: + type: integer + chatId: + type: string + mediaId: + type: string + description: WABA media id from uploadMedia + link: + type: string + description: Public URL to webp sticker + quotedMsgId: + type: string + additionalProperties: true + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendSticker + /sendCtaUrl: + post: + summary: Send CTA URL interactive message + description: Send an interactive message with a single call-to-action URL button. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - body + - displayText + - url + properties: + phone: + type: integer + chatId: + type: string + body: + type: string + displayText: + type: string + description: Button label + url: + type: string + format: uri + header: + type: object + additionalProperties: true + footer: + type: string + quotedMsgId: + type: string + additionalProperties: true + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendCtaUrl + /sendAddressMessage: + post: + summary: Send address request message + description: > + Request shipping address from the user (WhatsApp interactive + `address_message`). + + + **India and Singapore only.** Requires: + + - Business WhatsApp number registered in that country + + - Recipient phone matching the country (`+91` ↔ `IN`, `+65` ↔ `SG`) + + + Pass `country: "IN"` or `country: "SG"`. Eligibility is validated + upstream; + + mismatches (e.g. Singapore phone with `country: "IN"`) return errors + such as + + `Unsupported Interactive Message type` (HTTP 200 with `sent: false`). + + + Optional action parameters: `values`, `saved_addresses`, + `validation_errors`. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - body + properties: + phone: + type: integer + description: Recipient phone (E.164 digits, no +). Must match country. + chatId: + type: string + body: + type: string + description: Body text shown with the address request + country: + type: string + enum: + - IN + - SG + description: Address form country. Defaults to IN if omitted. + example: SG + values: + type: object + description: Optional prefilled address fields + additionalProperties: true + saved_addresses: + type: array + description: Optional previously saved addresses for the user + items: + type: object + additionalProperties: true + validation_errors: + type: object + description: Optional field validation errors when re-prompting + additionalProperties: true + quotedMsgId: + type: string + additionalProperties: true + examples: + singapore: + summary: Singapore + value: + phone: 6531650115 + body: Thanks for your order! Tell us what address you’d like this delivered to. + country: SG + india: + summary: India + value: + phone: 919876543210 + body: Thanks for your order! Tell us what address you’d like this delivered to. + country: IN + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendAddressMessage + /sendOrderDetails: + post: + summary: Send order details (India payments template) + description: > + Send a WhatsApp **order details** payment / invoice message using a + + pre-approved **Utility** template that has an `ORDER_DETAILS` button. + + + **India only** (WhatsApp Payments India). Requires: + + - India WhatsApp Business number + + - Commerce enabled on the channel (`GET`/`POST /commerce`) + + - Approved template with an `ORDER_DETAILS` button + + + Use this method when you need structured fields (`order`, `referenceId`, + + `currency`, `paymentSettings`). The API appends a template button + + `sub_type: order_details` and sends via the same path as `POST + /sendTemplate`. + + + Works **outside the 24-hour session window** (template message). + + + You can also send the same payload yourself with `POST /sendTemplate` by + + including a button component in `params`: + + + ```json + + { + "type": "button", + "sub_type": "order_details", + "index": 0, + "parameters": [{ + "type": "action", + "action": { + "order_details": { + "reference_id": "order-123", + "currency": "INR", + "order": { "status": "pending", "items": [], "subtotal": { "offset": 100, "value": 50000 } } + } + } + }] + } + + ``` + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - template + - namespace + - language + - order + properties: + phone: + type: integer + description: Recipient phone (India E.164 digits, no +). Use phone or chatId. + example: 919876543210 + chatId: + type: string + description: Recipient chatId (e.g. phone@c.us). Use phone or chatId. + template: + type: string + description: Approved Utility template name that includes an ORDER_DETAILS + button + example: order_details_utility + namespace: + type: string + description: Template namespace from the channel / template list + example: your_namespace_uuid + language: + type: object + description: Template language + required: + - code + properties: + code: + type: string + description: Language code + example: en + policy: + type: string + description: Optional language policy + example: deterministic + additionalProperties: true + params: + type: array + description: > + Extra template components (HEADER / BODY / etc.). If an + order_details button is missing, the API appends one from + order / referenceId / currency / paymentSettings. + items: + type: object + additionalProperties: true + referenceId: + type: string + description: Unique order / payment reference id (maps to reference_id) + example: order-123 + currency: + type: string + description: Currency code for India payments + example: INR + paymentSettings: + type: object + description: > + Optional payment settings (UPI / payment gateway / payment + link). Forwarded as payment_settings on the order_details + action. + additionalProperties: true + order: + type: object + description: > + Order payload for the ORDER_DETAILS button. Typical fields: + status, items[], subtotal, tax, shipping, discount. Amount + objects use `{ "offset": 100, "value": }` + (e.g. value 50000 with offset 100 = ₹500.00). + required: + - items + properties: + status: + type: string + description: Order status + example: pending + items: + type: array + description: Line items + items: + type: object + properties: + retailer_id: + type: string + example: SKU-1 + name: + type: string + example: Item + quantity: + type: integer + example: 1 + amount: + type: object + properties: + offset: + type: integer + example: 100 + value: + type: integer + example: 50000 + additionalProperties: true + additionalProperties: true + subtotal: + type: object + properties: + offset: + type: integer + example: 100 + value: + type: integer + example: 50000 + additionalProperties: true + tax: + type: object + additionalProperties: true + shipping: + type: object + additionalProperties: true + discount: + type: object + additionalProperties: true + additionalProperties: true + additionalProperties: true + example: + phone: 919876543210 + template: order_details_utility + namespace: your_namespace_uuid + language: + code: en + referenceId: order-123 + currency: INR + order: + status: pending + items: + - retailer_id: SKU-1 + name: Item + amount: + offset: 100 + value: 50000 + quantity: 1 + subtotal: + offset: 100 + value: 50000 + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendOrderDetails + /sendPaymentRequest: + post: + summary: Send payment request (regional) + description: > + Send a regional payment request interactive message (beta scaffold). + + `region` must be IN, SG, or BR. Payload shape follows Meta regional + payments docs; + + verify on stage before production use. Full regional builders are not + implemented yet. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - phone + - region + properties: + phone: + type: integer + region: + type: string + enum: + - IN + - SG + - BR + body: + type: string + interactive: + type: object + additionalProperties: true + action: + type: object + additionalProperties: true + additionalProperties: true + responses: + "200": + $ref: "#/components/responses/MessageSent" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: sendPaymentRequest + /retrieveMedia: + get: + summary: Retrieve uploaded media metadata + description: | + Get WABA media URL and metadata by mediaId (from uploadMedia). + The returned `url` is temporary and typically expires within ~5 minutes. + parameters: + - $ref: "#/components/parameters/token" + - name: mediaId + in: query + required: true + schema: + type: string + responses: + "200": + description: Media metadata + content: + application/json: + schema: + type: object + properties: + url: + type: string + mimeType: + type: string + sha256: + type: string + fileSize: + type: integer + id: + type: string + additionalProperties: true + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: retrieveMedia + /media/{mediaId}: + delete: + summary: Delete media from WABA storage + description: > + Delete previously uploaded media by numeric `mediaId` (from + `/uploadMedia`). + + + This is the canonical deletion endpoint and uses the REST `DELETE` verb + on the + + media resource path. The older `POST /deleteMedia` is a deprecated + alias. + parameters: + - $ref: "#/components/parameters/token" + - name: mediaId + in: path + required: true + description: Numeric WABA media id + schema: + type: string + responses: + "200": + $ref: "#/components/responses/Success" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: deleteMedia + /deleteMedia: + post: + summary: Delete media from WABA storage (deprecated alias) + deprecated: true + description: > + **Deprecated.** Use `DELETE /media/{mediaId}` instead. + + + This POST alias is kept for backward compatibility with earlier + integrations. + + New integrations should call `DELETE /media/{mediaId}`: 1msg follows + REST + + conventions for resource deletion going forward (delete a resource with + the + + `DELETE` verb on its resource path). + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - mediaId + properties: + mediaId: + type: string + description: Numeric WABA media id + additionalProperties: true + responses: + "200": + $ref: "#/components/responses/Success" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: deleteMediaLegacy + /blockUser: + post: + summary: Block WhatsApp user + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - phone + properties: + phone: + type: integer + additionalProperties: true + responses: + "200": + $ref: "#/components/responses/Success" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Users + operationId: blockUser + /unblockUser: + post: + summary: Unblock WhatsApp user + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - phone + properties: + phone: + type: integer + additionalProperties: true + responses: + "200": + $ref: "#/components/responses/Success" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Users + operationId: unblockUser + /blockedUsers: + get: + summary: List blocked WhatsApp users + description: > + Returns users currently blocked on this WhatsApp channel (WABA `GET + /block_users`). + + Same channel token auth as `blockUser` / `unblockUser`. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Blocked users list + content: + application/json: + schema: + type: object + properties: + blockedUsers: + type: array + items: + type: object + additionalProperties: true + additionalProperties: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Users + operationId: listBlockedUsers + /mmLiteStatus: + get: + summary: Get MM Lite availability and status + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: MM Lite status for the channel + content: + application/json: + schema: + type: object + properties: + available: + type: boolean + status: + type: string + message: + type: string + additionalProperties: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Messaging + operationId: getMmLiteStatus + /status: + get: + summary: Get channel status + description: > + Return WhatsApp Cloud API connection status for this channel + (`connected` + + when the number can send). May include `accountStatus`, messaging limit, + + and MM Lite availability. + + + Optional `health_status_warning` appears only when Meta Graph health + could + + not be loaded and a soft `connected` response is returned. Omitted on + the + + normal path and when health details are not applicable for the channel — + + never an internal provider/transport name. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Channel status + content: + application/json: + schema: + type: object + properties: + isCloud: + type: boolean + example: true + status: + type: string + description: typically `connected` when the number can send + example: connected + accountStatus: + type: string + example: authenticated + mm_lite_available: + type: boolean + health_status_warning: + type: string + description: Optional soft-fail warning when Meta Graph health_status could not + be loaded. Never includes internal provider names. + additionalProperties: true + example: + isCloud: true + status: connected + accountStatus: authenticated + mm_lite_available: false + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Channel + operationId: getStatus + /settings: + get: + summary: Get channel settings + description: > + Return channel settings: webhook URL(s), delivery flags, and analytics + toggles. + + + Auto-disable / circuit state matches `GET /webhook`. **Prefer + `webhookStatuses`** + + (one row per configured URL). The breaker is per URL, not per channel. + + + Channel-level `haveDisabledWebhooks` is `true` when **at least one** URL + in + + `webhookUrl` is sticky auto-disabled — not "the channel is dead". Live + URLs + + still receive events. Top-level `circuitState` / `reason` / `fails` / + `trips` + + are a compatibility summary of one URL (first sticky-disabled, else + first + + configured). + + + This is the same `GET /settings` endpoint as the REST API (previously + missing from MCP). + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Current settings + content: + application/json: + schema: + type: object + properties: + webhookUrl: + oneOf: + - type: string + - type: array + items: + type: string + description: One URL, or several if multiple webhooks are configured + guaranteedHooks: + type: boolean + description: Retry webhooks until acknowledged + ackNotificationsOn: + type: boolean + description: Include message ACK / status events in webhooks + rawHooks: + type: boolean + description: Forward raw provider webhook payloads + mm_lite_enabled: + type: boolean + template_analytics_enabled: + type: boolean + cta_url_link_tracking_opted_out: + type: boolean + haveDisabledWebhooks: + type: boolean + description: > + `true` when at least one URL in `webhookUrl` is sticky + auto-disabled. + + Not "the channel is dead": other configured URLs still + receive events. + circuitState: + type: string + description: > + Compatibility scalar for **one** URL (first + sticky-disabled, else first + + configured). Prefer `webhookStatuses[].circuitState`. + enum: + - ok + - degraded + - open + - half_open + - disabled + reason: + type: string + description: Last qualifying fail reason of the summary URL + fails: + type: integer + description: > + Diagnostic only (summary URL). Qualifying failures in the + rolling + + ~10 minute breaker window — not lifetime. Prefer + `webhookStatuses`. + trips: + type: integer + description: > + Diagnostic only (summary URL). Failed recovery probes (≈15 + min → + + 1 h → 6 h). Sticky disable after 3. Not a blind unblock + timer. + disabledAt: + type: integer + description: Epoch milliseconds when the summary URL became sticky-disabled + firstFailAt: + type: integer + description: > + Diagnostic only (summary URL). Epoch ms of the first fail + in the + + current ~10 minute window (pairs with `fails`). + lastStatus: + type: integer + description: Last HTTP status from the summary URL, when known + nextProbeAt: + type: integer + description: Epoch milliseconds of the next half-open probe for the summary URL, + if any + disabledWebhooks: + type: array + description: Currently configured URLs that are sticky auto-disabled (subset of + `webhookUrl`) + items: + type: object + additionalProperties: true + webhookStatuses: + type: array + description: One entry per currently configured URL. Prefer this over top-level + `haveDisabledWebhooks` / `circuitState`. + items: + $ref: "#/components/schemas/WebhookUrlStatus" + additionalProperties: true + example: + webhookUrl: https://example.com/webhook + guaranteedHooks: false + ackNotificationsOn: true + rawHooks: false + haveDisabledWebhooks: false + circuitState: ok + disabledWebhooks: [] + webhookStatuses: + - url: https://example.com/webhook + circuitState: ok + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Channel + operationId: listSettings + post: + summary: Update channel settings + description: > + Update channel settings. Only send fields you want to change. + + + `webhookUrl` **replaces** the stored list (one URL string, or up to 5 + URLs as an array). + + Passing an empty value clears webhooks. Repeating the same URL(s) also + clears a + + sticky auto-disable. With several URLs, send the complete array to + re-enable one + + of them — omitting a URL removes it from the channel. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + webhookUrl: + oneOf: + - type: string + - type: array + items: + type: string + description: Replacement webhook URL, or array of URLs (max 5) + guaranteedHooks: + type: boolean + ackNotificationsOn: + type: boolean + rawHooks: + type: boolean + template_analytics_enabled: + type: boolean + cta_url_link_tracking_opted_out: + type: boolean + additionalProperties: true + example: + webhookUrl: https://example.com/webhook + ackNotificationsOn: true + responses: + "200": + description: Updated settings + content: + application/json: + schema: + type: object + additionalProperties: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Channel + operationId: createSettings + /commerce: + get: + summary: Get Commerce Settings + description: > + Returns catalog/cart commerce settings for the channel. + + + Fails with a provider error when the WABA has no catalog linked — that + is an + + API/account limitation, not an MCP schema bug. + + + - `is_catalog_visible` — show catalog storefront icon (`true`) or hide + it (`false`). + + - `is_cart_enabled` — enable cart (`true`) or disable it (`false`). + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Commerce settings + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + description: Catalog id + is_cart_enabled: + type: boolean + description: Whether the cart is enabled + is_catalog_visible: + type: boolean + description: Whether the catalog storefront icon is shown + additionalProperties: true + example: + - id: "789887292550821" + is_cart_enabled: true + is_catalog_visible: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Catalog + operationId: getCommerce + post: + summary: Set Commerce Settings + description: > + Update catalog/cart commerce settings via the `params` object. + + + - `params.is_catalog_visible` — show catalog storefront icon (`true`) or + hide it (`false`). + + - `params.is_cart_enabled` — enable cart (`true`) or disable it + (`false`). + + + Blocked when the channel subscription limit is exceeded. + + Requires a commerce-capable channel (Cloud Functions `/commerceWAV2`). + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - params + properties: + params: + type: object + required: + - is_cart_enabled + - is_catalog_visible + properties: + is_cart_enabled: + type: boolean + description: Enable cart + is_catalog_visible: + type: boolean + description: Show catalog storefront icon + additionalProperties: true + additionalProperties: true + example: + params: + is_cart_enabled: true + is_catalog_visible: true + responses: + "200": + description: Commerce updated + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + additionalProperties: true + example: + success: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Catalog + operationId: createCommerce + /conversationalAutomation: + get: + summary: Get conversational automation settings + description: > + Get WhatsApp conversational components for the channel (welcome message, + + ice-breaker prompts, and slash commands). + + + Proxies Meta/360dialog `GET /conversational_automation`. + + + When `enable_welcome_message` is true and a user opens chat for the + first + + time, Meta delivers a webhook message with `type: request_welcome`. The + + inbound formatter exposes that as `type: "request_welcome"` and + + `meta.request_welcome: true` so your webhook can send a custom welcome + reply. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Conversational automation settings + content: + application/json: + schema: + $ref: "#/components/schemas/ConversationalAutomation" + example: + enable_welcome_message: true + prompts: + - How can I help? + commands: + - command_name: help + command_description: Get support options + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Channel + operationId: getConversationalAutomation + post: + summary: Set conversational automation settings + description: | + Update WhatsApp conversational components. + + Allowed body fields (others are ignored): + - `enable_welcome_message` (boolean) + - `prompts` (string[], max 4, each ≤ 80 chars) + - `commands` (`{ command_name, command_description }[]`) + + Proxies Meta/360dialog `POST /conversational_automation`. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ConversationalAutomation" + example: + enable_welcome_message: true + prompts: + - How can I help? + commands: + - command_name: help + command_description: Get support options + responses: + "200": + description: > + Updated settings from WABA, or legacy response.error wrapper on + upstream failure. + content: + application/json: + schema: + type: object + additionalProperties: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Channel + operationId: setConversationalAutomation + /webhook: + get: + summary: Get webhook URL + description: > + Return the webhook URL(s) currently configured for this channel, plus + delivery + + status for each URL. + + + `webhookUrl` is a string when a single URL is set, an array of strings + when + + multiple URLs are configured, or `null` if none is set. + + + **Prefer `webhookStatuses`.** The circuit breaker is per URL, not per + channel. + + If 1 of 4 URLs is sticky-disabled, that URL is skipped; the other three + still + + receive events. Send/receive are not affected. + + + Channel-level `haveDisabledWebhooks`, `circuitState`, `reason`, `fails`, + + `trips`, and timestamps are a compatibility summary of **one** URL (the + first + + sticky-disabled URL, else the first configured URL). They are not "the + channel + + is dead". + + + - `haveDisabledWebhooks` — `true` when **at least one** URL in + `webhookUrl` is + sticky auto-disabled. Live URLs still get events. + - `circuitState` — compressed scalar for that same primary URL: + `ok` | `degraded` | `open` | `half_open` | `disabled`. + - `disabledWebhooks` — currently configured URLs that are + sticky-disabled + (subset of `webhookUrl`). + - `webhookStatuses` — one row per currently configured URL. Use this + array + to see which endpoints are live vs skipped. + - `fails` / `trips` / `firstFailAt` — **diagnostics for support**, not + product + UI. `fails` = failures in a rolling ~10 minute window (not lifetime). + `trips` = failed recovery probes on the 15 min → 1 h → 6 h schedule (sticky + disable after 3). Clients can ignore these and use `webhookStatuses` + + `disabledWebhooks` only. + + Sticky disable skips delivery; it does not unconfigure the URL (it stays + in + + `webhookUrl`). Re-enable with `POST /webhook` or `POST /settings`. Those + + endpoints **replace** the whole list — with multiple URLs, send the full + array. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Current webhook URL and per-URL circuit status + content: + application/json: + schema: + type: object + properties: + webhookUrl: + oneOf: + - type: string + format: uri + - type: array + items: + type: string + format: uri + - type: "null" + description: Configured webhook URL, array of URLs, or null when none is set + haveDisabledWebhooks: + type: boolean + description: > + `true` when at least one URL in `webhookUrl` is sticky + auto-disabled. + + Not "the channel is dead": other configured URLs still + receive events. + + Send/receive are unaffected. + circuitState: + type: string + description: > + Compatibility scalar for **one** URL (first + sticky-disabled, else first + + configured). Not the channel as a whole. Prefer + `webhookStatuses[].circuitState`. + enum: + - ok + - degraded + - open + - half_open + - disabled + reason: + type: string + description: Last qualifying fail reason of the summary URL (same URL as + `circuitState`) + fails: + type: integer + description: > + Diagnostic only (summary URL). Qualifying failures in the + rolling + + ~10 minute breaker window — not lifetime and not customer + retries. + + Prefer `webhookStatuses` / `disabledWebhooks`. + trips: + type: integer + description: > + Diagnostic only (summary URL). Failed recovery probes (≈15 + min → + + 1 h → 6 h). Sticky disable after 3. Not a blind unblock + timer. + disabledAt: + type: integer + description: Epoch milliseconds when the summary URL became sticky-disabled + firstFailAt: + type: integer + description: > + Diagnostic only (summary URL). Epoch ms of the first fail + in the + + current ~10 minute window (pairs with `fails`). + lastStatus: + type: integer + description: Last HTTP status from the summary URL, when known + nextProbeAt: + type: integer + description: Epoch milliseconds of the next half-open probe for the summary URL, + if any + disabledWebhooks: + type: array + description: > + Currently configured URLs that are sticky auto-disabled + (subset of `webhookUrl`). + + Does not include leftover URLs that are no longer + configured. + items: + type: object + additionalProperties: true + webhookStatuses: + type: array + description: > + One entry per currently configured URL. Prefer this over + top-level + + `haveDisabledWebhooks` / `circuitState` when more than one + webhook is set. + items: + $ref: "#/components/schemas/WebhookUrlStatus" + additionalProperties: true + examples: + healthy: + summary: Single URL delivering + value: + webhookUrl: https://example.com/webhook + haveDisabledWebhooks: false + circuitState: ok + disabledWebhooks: [] + webhookStatuses: + - url: https://example.com/webhook + circuitState: ok + mixed: + summary: One of several URLs sticky-disabled + value: + webhookUrl: + - https://live.example.com/webhook + - https://down.example.com/webhook + haveDisabledWebhooks: true + circuitState: disabled + reason: http_404 + fails: 1 + trips: 3 + disabledWebhooks: + - url: https://down.example.com/webhook + reason: http_404 + state: disabled + fails: 1 + trips: 3 + webhookStatuses: + - url: https://live.example.com/webhook + circuitState: ok + - url: https://down.example.com/webhook + circuitState: disabled + reason: http_404 + fails: 1 + trips: 3 + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Webhooks + operationId: getWebhook + post: + summary: Set webhook URL + description: > + Set the client webhook URL that receives inbound events for this + channel. + + + **Replaces** the stored webhook list (it does not append). To deliver + the same + + events to several endpoints, pass an array in `webhookUrl` (max 5) or + use + + `POST /settings`. With several URLs already configured, posting only the + + disabled URL **drops the others** — send the complete array to re-enable + one + + of many. + + + Incoming payloads include messages, delivery ACKs, group updates, + coexistence + + echoes, and calling events. WhatsApp Calling payloads (`type: "calls"`) + are + + documented under the **Calling** tag. Call permission replies arrive on + the + + normal messages path (`call_permission_reply`). + + + Repeating `POST /webhook` with the same URL(s) clears an auto-disable + (self-serve + + re-enable). Send/receive are not affected by webhook auto-disable. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - webhookUrl + properties: + webhookUrl: + oneOf: + - type: string + format: uri + - type: array + items: + type: string + format: uri + maxItems: 5 + description: > + HTTP or HTTPS URL, or array of URLs (max 5). Replaces the + stored list. + + Empty values are rejected — clear webhooks with `POST + /settings`. + additionalProperties: true + examples: + single: + summary: Single URL + value: + webhookUrl: https://example.com/webhook + multiple: + summary: Several URLs (replaces the list) + value: + webhookUrl: + - https://example.com/webhook + - https://backup.example.com/webhook + responses: + "200": + description: Webhook configured + content: + application/json: + schema: + type: object + properties: + set: + type: boolean + description: True when the stored webhook URL(s) match the request + webhookUrl: + oneOf: + - type: string + - type: array + items: + type: string + description: Currently stored webhook URL or list + additionalProperties: true + example: + set: true + webhookUrl: https://example.com/webhook + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Webhooks + operationId: setWebhook + /callingSettings: + get: + summary: Get calling settings + description: > + Return WhatsApp Calling API settings for this channel (beta). + + + Proxies upstream `GET /calling/settings`. + + + **Prerequisites** + + - Number must be eligible for Meta Calling (Cloud API; not COEX) + + - Trial / `subscriptionBlocked` channels receive **403** plain text + + - You need your own WebRTC or SIP stack; 1msg is a **signaling proxy** + only + and does **not** store call history or recordings + + See the **Calling** tag overview for inbound/outbound flows and + webhooks. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Calling settings (and possibly other Cloud API settings Meta returns) + content: + application/json: + schema: + $ref: "#/components/schemas/CallingSettings" + example: + calling: + status: ENABLED + call_icon_visibility: DEFAULT + callback_permission_status: ENABLED + sip: + status: DISABLED + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Calling + operationId: getCallingSettings + post: + summary: Update calling settings + description: > + Enable, disable, or update WhatsApp Calling settings (beta). + + + Proxies upstream `POST /calling/settings`. + + Body is forwarded as-is (1msg does not validate fields). + + + **Common fields under `calling`** + + - `status` (`ENABLED` | `DISABLED`) — required to turn calling on/off + + - `call_icon_visibility` (`DEFAULT` | `DISABLE_ALL`) — optional + + - `callback_permission_status` (`ENABLED` | `DISABLED`) — optional; + when enabled, inbound user calls grant callback permission + - `call_hours` — optional hours / timezone object + + - `sip` — optional SIP trunk; when SIP is ENABLED, Graph call actions + and + calling webhooks are not used + - `srtp_key_exchange_protocol` (`DTLS` | `SDES`) — SDES only with SIP + + - `video.status` — optional + + + Meta may accept only one feature group per request — prefer focused + updates + + (e.g. enable status first, then SIP). + + + Trial / `subscriptionBlocked` → **403** plain text. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CallingSettings" + examples: + enable: + summary: Enable calling + value: + calling: + status: ENABLED + call_icon_visibility: DEFAULT + callback_permission_status: ENABLED + disable: + summary: Disable calling + value: + calling: + status: DISABLED + enableSip: + summary: Enable SIP (disables Graph call webhooks) + value: + calling: + status: ENABLED + sip: + status: ENABLED + servers: + - hostname: sip.example.com + port: 5061 + responses: + "200": + description: | + Usually `{ "success": true }` from upstream, or legacy + `{ "result": "success" }` / `{ "response": { "error": "..." } }`. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + result: + type: string + example: success + response: + type: object + properties: + error: + type: string + additionalProperties: true + additionalProperties: true + example: + success: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Calling + operationId: updateCallingSettings + /initiateCall: + post: + summary: Call action (connect / pre_accept / accept / reject / terminate) + description: > + Perform a WhatsApp Calling action (beta). + + + Proxies upstream `POST /calling/calls`. Despite the historical path + + name `/initiateCall`, this endpoint handles **all** call actions: + + + | action | Use | Required | + + |--------|-----|----------| + + | `connect` | Outbound business → user | `to` + `session` (`sdp_type: + offer`) | + + | `pre_accept` | Inbound (optional, reduces audio clipping) | `call_id` + + `session` (`sdp_type: answer`) | + + | `accept` | Inbound answer | `call_id` + `session` (`sdp_type: answer`) + | + + | `reject` | Decline inbound | `call_id` | + + | `terminate` | Hang up | `call_id` | + + + **SDP / media (critical)** + + - `accept` / `pre_accept` require a **WebRTC-generated SDP answer**. + + - Do **not** send Meta's offer SDP back as the answer. + + - Postman (or curl) alone **cannot** establish real media — you need a + WebRTC or SIP stack. 1msg only proxies signaling. + + Answer within ~**30–60 seconds** of an inbound `connect` webhook or Meta + + terminates as unanswered. Common Meta errors include Calling not enabled + + (`138000`), no permission (`138006`), SDP validation failures. + + + **Outbound** requires a prior Call Permission Request (CPR) acceptance. + + See the **Calling** tag overview for the full outbound flow and CPR + limits. + + + Trial / `subscriptionBlocked` → **403** plain text. + + Upstream failures often return HTTP 200 with `{ "response": { "error": + "" } }`. + + `Invalid API KEY` is used only when Meta/360 body actually indicates an + + invalid API key or token. Other upstream 401s (notably call permission + + `138006`) pass through `error.message` / `error_user_msg`. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InitiateCallRequest" + examples: + outboundConnect: + summary: Outbound connect (business SDP offer) + value: + messaging_product: whatsapp + to: "12185552828" + action: connect + biz_opaque_callback_data: order-123 + session: + sdp_type: offer + sdp: "[REPLACE_WITH_WEBRTC_SDP]" + preAccept: + summary: Inbound pre_accept (WebRTC answer SDP) + value: + messaging_product: whatsapp + call_id: wacid.ABGGFjFVU2AfAgo6V-Hc5eCgK5Gh + action: pre_accept + session: + sdp_type: answer + sdp: "[REPLACE_WITH_WEBRTC_SDP]" + accept: + summary: Inbound accept (WebRTC answer SDP) + value: + messaging_product: whatsapp + call_id: wacid.ABGGFjFVU2AfAgo6V-Hc5eCgK5Gh + action: accept + session: + sdp_type: answer + sdp: "[REPLACE_WITH_WEBRTC_SDP]" + reject: + summary: Reject inbound call + value: + messaging_product: whatsapp + call_id: wacid.ABGGFjFVU2AfAgo6V-Hc5eCgK5Gh + action: reject + terminate: + summary: Terminate active call + value: + messaging_product: whatsapp + call_id: wacid.ABGGFjFVU2AfAgo6V-Hc5eCgK5Gh + action: terminate + responses: + "200": + description: Call action result from upstream, or legacy error wrapper + content: + application/json: + schema: + $ref: "#/components/schemas/InitiateCallResponse" + examples: + success: + summary: Accept / pre_accept / reject / terminate success + value: + messaging_product: whatsapp + success: true + connectSuccess: + summary: Outbound connect success (includes call id) + value: + messaging_product: whatsapp + calls: + - id: wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA + upstreamError: + summary: Upstream error wrapped by 1msg + value: + response: + error: Calling not enabled + callPermissionDenied: + summary: Meta 138006 (upstream 401, not Invalid API KEY) + value: + response: + error: No approved call permission from the recipient + invalidApiKey: + summary: Real invalid D360/Meta token + value: + response: + error: Invalid API KEY + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Calling + operationId: initiateCall + /groups: + post: + summary: Create Group + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - groupName + properties: + groupName: + type: string + description: Group subject / name + description: + type: string + description: Optional group description + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + error: + description: Error details returned by the provider. May be a string or a JSON + object. + additionalProperties: true + tags: + - Groups + operationId: createGroups + get: + summary: Get Groups List + description: List WhatsApp groups for this channel. Paginate with `limit`, + `before`, and `after`. + parameters: + - $ref: "#/components/parameters/token" + - name: limit + in: query + schema: + type: integer + description: Max groups to return + - name: before + in: query + schema: + type: string + description: Cursor for the previous page + - name: after + in: query + schema: + type: string + description: Cursor for the next page + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Groups + operationId: listGroups + /groups/{groupId}: + get: + summary: Get Group Info + parameters: + - $ref: "#/components/parameters/token" + - name: fields + in: query + schema: + type: string + description: Comma-separated group fields to return + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Groups + operationId: getGroupsGroupId + parameters: + - name: groupId + in: path + required: true + schema: + type: string + example: "{{createdGroupId}}" + post: + summary: Update Group Info + description: | + Update group subject, description, and/or profile picture. + Path `groupId` is the WABA group id (without `@g.us`). + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + subject: + type: string + description: New group subject / name + description: + type: string + description: New group description + profile_picture_file: + type: string + description: Image URL or base64 data URI for the group profile picture + example: + subject: Support team + description: Customer support + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Groups + operationId: createGroupsGroupId + delete: + summary: Delete Group + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + response: + type: object + additionalProperties: true + additionalProperties: true + tags: + - Groups + operationId: deleteGroupsGroupId + /groups/{groupId}/inviteLink: + get: + summary: Get Invite Link + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Groups + operationId: getGroupsGroupIdInvitelink + parameters: + - name: groupId + in: path + required: true + schema: + type: string + example: "{{createdGroupId}}" + post: + summary: Reset Invite Link + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Groups + operationId: createGroupsGroupIdInvitelink + /whatsapp_business_encryption: + get: + summary: Get business encryption public key + description: > + Retrieve the WhatsApp business public key and signature status for this + channel's + + phone number. Required before publishing or sending Flows that use data + encryption. + parameters: + - $ref: "#/components/parameters/token" + responses: + "200": + description: Business encryption key status + content: + application/json: + schema: + type: object + properties: + business_public_key: + type: string + description: Stored 2048-bit RSA public key (PEM) + business_public_key_signature_status: + type: string + enum: + - VALID + - MISMATCH + data: + type: array + items: + type: object + additionalProperties: true + additionalProperties: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Flows + operationId: getWhatsappBusinessEncryption + post: + summary: Set business encryption public key + description: > + Upload and sign a 2048-bit RSA business public key (PEM) for this + channel's phone number. + + Meta requires a signed key before Flow publish/send. Only one active key + per number; + + a new upload replaces the previous key. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - business_public_key + properties: + business_public_key: + type: string + description: 2048-bit RSA public key in PEM format + example: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... + -----END PUBLIC KEY----- + responses: + "200": + description: Public key uploaded and signed + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + additionalProperties: true + "400": + description: Missing or invalid PEM key + content: + application/json: + schema: + type: object + properties: + error: + type: string + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Flows + operationId: setWhatsappBusinessEncryption + /flows: + post: + summary: Create Flow + description: > + Create a WhatsApp Flow (`name` + `categories` required). + + Currently returns HTTP 501 until the Meta Graph Flows rewrite is + available. + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - categories + properties: + name: + type: string + description: Flow name + categories: + type: array + items: + type: string + enum: + - SIGN_UP + - SIGN_IN + - APPOINTMENT_BOOKING + - LEAD_GENERATION + - CONTACT_US + - CUSTOMER_SUPPORT + - SURVEY + - OTHER + description: One or more Flow categories + endpointUri: + type: string + description: HTTPS endpoint for the Flow data channel + publish: + type: boolean + description: Publish immediately after create + flowJson: + type: object + additionalProperties: true + description: Optional Flow JSON document + example: + name: lead_form + categories: + - LEAD_GENERATION + publish: false + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + error: + description: Error details returned by the provider. May be a string or a JSON + object. + additionalProperties: true + "501": + description: WhatsApp Flows are not available on this channel yet + tags: + - Flows + operationId: createFlows + get: + summary: List Flows + description: > + List WhatsApp Flows for the WABA. + + Currently returns HTTP 501 until the Meta Graph Flows rewrite is + available. + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + - name: fields + in: query + schema: + type: string + description: Comma-separated Flow fields to return + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + type: object + additionalProperties: true + paging: + type: object + additionalProperties: true + count: + type: integer + total: + type: integer + error: + description: Error details returned by the provider. May be a string or a JSON + object. + additionalProperties: true + "501": + description: WhatsApp Flows are not available on this channel yet + tags: + - Flows + operationId: listFlows + /flows/{flowId}: + get: + summary: Get Flow Details + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + error: + description: Error details returned by the provider. May be a string or a JSON + object. + additionalProperties: true + tags: + - Flows + operationId: getFlowsFlowId + parameters: + - $ref: "#/components/parameters/flowId" + example: "{{flowId}}" + delete: + summary: Delete Flow + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + error: + description: Error details returned by the provider. May be a string or a JSON + object. + additionalProperties: true + tags: + - Flows + operationId: deleteFlowsFlowId + /flows/{flowId}/metadata: + patch: + summary: Update Flow Metadata + description: > + Update Flow `name`, `categories`, and/or `endpointUri`. + + Currently returns HTTP 501 until the Meta Graph Flows rewrite is + available. + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + categories: + type: array + items: + type: string + enum: + - SIGN_UP + - SIGN_IN + - APPOINTMENT_BOOKING + - LEAD_GENERATION + - CONTACT_US + - CUSTOMER_SUPPORT + - SURVEY + - OTHER + endpointUri: + type: string + description: HTTPS endpoint for the Flow data channel + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + "501": + description: WhatsApp Flows are not available on this channel yet + tags: + - Flows + operationId: patchFlowsFlowIdMetadata + parameters: + - $ref: "#/components/parameters/flowId" + example: "{{flowId}}" + /flows/{flowId}/assets: + patch: + summary: Update Flow Structure + description: > + Replace the Flow JSON document (`flowJson`). + + Currently returns HTTP 501 until the Meta Graph Flows rewrite is + available. + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - flowJson + properties: + flowJson: + type: object + additionalProperties: true + description: WhatsApp Flow JSON document + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + validationErrors: + type: array + items: + type: object + additionalProperties: true + error: + description: Error details returned by the provider. May be a string or a JSON + object. + additionalProperties: true + "501": + description: WhatsApp Flows are not available on this channel yet + tags: + - Flows + operationId: patchFlowsFlowIdAssets + parameters: + - $ref: "#/components/parameters/flowId" + example: "{{flowId}}" + /flows/{flowId}/preview: + get: + summary: Preview Flow + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Flows + operationId: getFlowsFlowIdPreview + parameters: + - $ref: "#/components/parameters/flowId" + example: "{{flowId}}" + /flows/{flowId}/publish: + post: + summary: Publish Flow + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Flows + operationId: createFlowsFlowIdPublish + parameters: + - $ref: "#/components/parameters/flowId" + example: "{{flowId}}" + /flows/{flowId}/deprecate: + post: + summary: Deprecate Flow + parameters: + - $ref: "#/components/parameters/token" + - name: wabaAccountId + in: query + schema: + type: string + example: "{{wabaAccountId}}" + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: true + tags: + - Flows + operationId: createFlowsFlowIdDeprecate + parameters: + - $ref: "#/components/parameters/flowId" + example: "{{flowId}}" + /templates: + get: + summary: Get templates list + description: List message templates. Paginate with `limit` / `offset`; sort by + `id`, `name`, or `status`. + parameters: + - $ref: "#/components/parameters/token" + - name: limit + in: query + schema: + type: integer + - name: offset + in: query + schema: + type: integer + - name: sort + in: query + schema: + type: string + enum: + - id + - name + - status + description: Sort field (`id`, `name`, or `status`) + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + total: + type: integer + templates: + type: array + items: + type: object + additionalProperties: true + error: + type: object + additionalProperties: true + additionalProperties: true + tags: + - Templates + operationId: listTemplates + /addTemplate: + post: + summary: Create message template + description: > + Create a WABA message template (`name`, `category`, `language`, + `components`). + + After create, Meta reviews the template; list it with `GET /templates`. + parameters: + - $ref: "#/components/parameters/token" + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: Template name + example: hello_world + category: + type: string + description: MARKETING, UTILITY, or AUTHENTICATION + example: UTILITY + language: + type: string + description: Template language code (e.g. en_US) + example: en_US + components: + type: array + items: + type: object + additionalProperties: true + description: Template components (HEADER, BODY, FOOTER, BUTTONS, CAROUSEL) + additionalProperties: true + example: + name: hello_world + category: UTILITY + language: en_US + components: + - type: BODY + text: Hello {{1}} + responses: + "200": + description: Template created. Provider payload is wrapped in `response`. + content: + application/json: + schema: + type: object + properties: + response: + type: object + description: Upstream create-template payload + additionalProperties: true + additionalProperties: true + example: + response: + id: "123456789" + status: PENDING + category: MARKETING + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Templates + operationId: addTemplate + /removeTemplate: + post: + summary: Remove message template + description: Delete a message template by `name` (all language versions of that name). + parameters: + - $ref: "#/components/parameters/token" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + properties: + name: + type: string + description: Template name to delete + example: + name: hello_world + responses: + "200": + description: Template removed + content: + application/json: + schema: + type: object + additionalProperties: true + "401": + $ref: "#/components/responses/InvalidToken" + "500": + $ref: "#/components/responses/InternalError" + tags: + - Templates + operationId: removeTemplate +components: + parameters: + flowId: + name: flowId + in: path + required: true + schema: + type: string + description: Flow ID + token: + name: token + in: query + required: true + schema: + type: string + description: JWT token or API key for authorization + date: + name: date + in: query + required: false + schema: + type: string + format: date + description: Filter by date (YYYY-MM-DD) + groupId: + name: groupId + in: path + required: true + schema: + type: string + description: Group ID (phone number with country code) + schemas: + ErrorResponse: + type: object + properties: + error: + type: string + description: Error message + description: Standard error response + MessageSentResponse: + type: object + properties: + sent: + type: boolean + description: Whether the message was successfully queued for sending + id: + type: string + description: Message ID + message: + type: string + description: Status message + description: + type: string + description: Detailed description + additionalProperties: true + description: Response for successfully sent message + SuccessResponse: + type: object + properties: + result: + type: string + description: Operation result + additionalProperties: true + description: Generic success response + ProfileInfo: + type: object + properties: + about: + type: string + description: Profile's About section + address: + type: string + description: Address of the business + description: + type: string + description: Description of the business + email: + type: string + description: Business email + phone: + type: string + description: Linked phone number + vertical: + type: string + enum: + - Automotive + - Beauty, Spa and Salon + - Clothing and Apparel + - Education + - Entertainment + - Event Planning and Service + - Finance and Banking + - Food and Grocery + - Public Service + - Hotel and Lodging + - Medical and Health + - Non-profit + - Professional Services + - Shopping and Retail + - Travel and Transportation + - Restaurant + - Other + description: Industry of the business + photo: + type: string + description: Profile photo URL + websites: + type: array + items: + type: string + additionalProperties: true + description: WhatsApp Business profile information + ConversationalAutomation: + type: object + properties: + enable_welcome_message: + type: boolean + description: Enable the automatic welcome message for new conversations + prompts: + type: array + maxItems: 4 + description: Ice-breaker prompt strings (max 4, each ≤ 80 characters) + items: + type: string + maxLength: 80 + commands: + type: array + maxItems: 30 + description: Slash commands shown in WhatsApp + items: + type: object + required: + - command_name + - command_description + properties: + command_name: + type: string + maxLength: 32 + command_description: + type: string + maxLength: 256 + additionalProperties: true + additionalProperties: true + CallingSettings: + type: object + description: > + Calling settings payload for GET/POST `/callingSettings`. + + Proxies upstream `GET|POST /calling/settings`. + + Extra Meta fields may appear on GET responses; document core fields + below. + properties: + calling: + type: object + description: Calling feature configuration for the business phone number + properties: + status: + type: string + description: Enable or disable Calling API on this number + enum: + - ENABLED + - DISABLED + example: ENABLED + call_icon_visibility: + type: string + description: Controls call icon visibility in the WhatsApp client + enum: + - DEFAULT + - DISABLE_ALL + example: DEFAULT + callback_permission_status: + type: string + description: > + When ENABLED, a user who calls your business automatically + grants + + call permission for business-initiated callbacks (subject to + Meta rules). + enum: + - ENABLED + - DISABLED + example: ENABLED + srtp_key_exchange_protocol: + type: string + description: | + SRTP key exchange. DTLS is default/recommended. + SDES is only valid when SIP signaling is enabled. + enum: + - DTLS + - SDES + example: DTLS + call_hours: + type: object + description: Optional call hours / timezone configuration (Meta shape) + additionalProperties: true + properties: + status: + type: string + example: ENABLED + timezone: + type: string + example: America/Los_Angeles + day_of_week_start: + type: string + example: MONDAY + sip: + type: object + description: > + SIP trunk settings. When SIP is ENABLED, Graph call actions and + + calling webhooks are not used — Meta dials your SIP server + directly. + properties: + status: + type: string + enum: + - ENABLED + - DISABLED + example: DISABLED + servers: + type: array + items: + type: object + properties: + hostname: + type: string + example: sip.example.com + port: + type: integer + example: 5061 + request_uri_user_params: + type: object + additionalProperties: + type: string + sip_user_password: + type: string + description: Present on GET when SIP credentials are returned by Meta + password: + type: string + description: Alternate password field name from some Meta responses + app_id: + type: string + additionalProperties: true + additionalProperties: true + video: + type: object + description: Video calling toggle when supported + properties: + status: + type: string + enum: + - ENABLED + - DISABLED + additionalProperties: true + audio: + type: object + description: Audio settings (typically response-only) + properties: + status: + type: string + additionalProperties: true + restrictions: + type: object + description: Calling restrictions (response-only) + additionalProperties: true + ip_addresses: + type: object + description: Meta media IP ranges (response-only) + additionalProperties: true + call_icons: + type: object + description: Call icon country restrictions (response-only) + additionalProperties: true + additionalProperties: true + additionalProperties: true + InitiateCallRequest: + type: object + description: > + Call action payload for `POST /initiateCall` (proxies upstream `POST + /calling/calls`). + + + Despite the path name, this endpoint handles **all** call actions — not + only + + outbound connect. + + + ## Required fields by `action` + + + | action | Required | `session` | + + |--------|----------|-----------| + + | `connect` (outbound) | `messaging_product`, `to`, `action` | + `sdp_type: offer` + business WebRTC SDP | + + | `pre_accept` | `messaging_product`, `call_id`, `action` | `sdp_type: + answer` (WebRTC-generated) | + + | `accept` | `messaging_product`, `call_id`, `action` | `sdp_type: + answer` (WebRTC-generated) | + + | `reject` | `messaging_product`, `call_id`, `action` | none | + + | `terminate` | `messaging_product`, `call_id`, `action` | none | + + + **Critical:** For `pre_accept` / `accept`, `session.sdp` must be a + + **WebRTC-generated SDP answer**. Do not echo Meta's offer SDP back. + + Postman alone cannot establish real media — you need a WebRTC (or SIP) + stack. + required: + - messaging_product + - action + properties: + messaging_product: + type: string + description: Must be `whatsapp` + enum: + - whatsapp + example: whatsapp + action: + type: string + description: Call control action + enum: + - connect + - pre_accept + - accept + - reject + - terminate + example: connect + call_id: + type: string + description: > + WhatsApp call id from the inbound/outbound calls webhook + (`calls[].id`). + + Required for `pre_accept`, `accept`, `reject`, `terminate`. + example: wacid.ABGGFjFVU2AfAgo6V-Hc5eCgK5Gh + to: + type: string + description: | + Recipient WhatsApp user phone (digits, country code, no +). + Required for outbound `connect`. + example: "12185552828" + biz_opaque_callback_data: + type: string + description: Optional opaque string echoed on later call webhooks for correlation + maxLength: 512 + example: order-123-callback + session: + type: object + description: WebRTC SDP session (required for connect / pre_accept / accept) + required: + - sdp_type + - sdp + properties: + sdp_type: + type: string + description: offer for outbound connect; answer for pre_accept/accept + enum: + - offer + - answer + example: offer + sdp: + type: string + description: > + Full SDP (RFC 8866). Replace placeholders with SDP from your + WebRTC stack. + + Must negotiate ICE, DTLS-SRTP, and OPUS for WhatsApp media. + example: "[REPLACE_WITH_WEBRTC_SDP]" + additionalProperties: true + additionalProperties: true + InitiateCallResponse: + type: object + description: > + Successful call action response from upstream, returned as-is. + + On empty upstream body, 1msg may return `{ "result": "success" }`. + + Upstream failures are often wrapped as HTTP 200 `{ "response": { + "error": "..." } }`. + properties: + messaging_product: + type: string + example: whatsapp + success: + type: boolean + example: true + calls: + type: array + description: Present on outbound `connect` — contains the new call id + items: + type: object + properties: + id: + type: string + example: wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA + additionalProperties: true + result: + type: string + description: Legacy success marker when upstream returns an empty body + example: success + response: + type: object + description: Present when 1msg wraps an upstream error + properties: + error: + type: string + additionalProperties: true + additionalProperties: true + WebhookUrlStatus: + type: object + description: | + Circuit / auto-disable status for one configured webhook URL. + The breaker is per URL (`instanceId` + URL hash), not per channel. + required: + - url + - circuitState + properties: + url: + type: string + format: uri + description: This webhook URL (same string as in `webhookUrl`) + circuitState: + type: string + description: > + Delivery state for this URL only. `ok` delivering; `degraded` recent + qualifying fails (still delivering, warning email on the 2nd fail); + `open` skipping most deliveries pending a probe; `half_open` probe + in flight (resume only on HTTP 2xx); `disabled` sticky auto-disabled + until self-serve re-enable (`POST /webhook` / `POST /settings` with + this URL) or a successful probe before sticky disable. + enum: + - ok + - degraded + - open + - half_open + - disabled + reason: + type: string + description: Last qualifying fail reason, e.g. `http_404` or `timeout_or_network` + fails: + type: integer + description: > + Diagnostic only — safe for clients to ignore. Number of qualifying + delivery + + failures in the rolling ~10 minute breaker window (not lifetime, not + "how many + + times the customer retried"). Prefer `circuitState` / + `disabledWebhooks`. + trips: + type: integer + description: > + Diagnostic only — safe for clients to ignore. How many recovery + probes already + + failed for this URL. Probes run on a schedule (≈15 min → 1 h → 6 h); + sticky + + disable after 3 failed probes. The 6 h step is the last probe, not a + blind + + "unblock in 6 hours" timer. + disabledAt: + type: integer + description: Epoch milliseconds when this URL became sticky-disabled + firstFailAt: + type: integer + description: > + Diagnostic only. Epoch ms of the first fail in the current ~10 + minute window + + (pairs with `fails`; not a customer-facing period). + lastStatus: + type: integer + description: Last HTTP status from this URL, when known + nextProbeAt: + type: integer + description: Epoch milliseconds of the next half-open probe, if any + additionalProperties: true + SendMessageRequest: + type: object + required: + - body + properties: + body: + type: string + description: Message text, UTF-8 or UTF-16 string with emoji + minLength: 1 + maxLength: 4096 + example: Hello World 🌍 + quotedMsgId: + type: string + description: Message ID to quote/reply to (Cloud API wamid) + pattern: ^wamid\..+$ + example: wamid.HBgNMTIwMjA3MjEzNjkVAgARGBI5RTBCNUY0QUE2RjZBQzhGNDkA + chatId: + type: string + description: "Recipient chat ID: phone@c.us, group@g.us, or BSUID@lid" + example: 12020721369@c.us + phone: + type: integer + description: Phone number starting with country code (alternative to chatId) + minimum: 1000000000 + maximum: 999999999999999 + example: 12020721369 + additionalProperties: false + description: Request body for sending a text message + SendFileRequest: + type: object + properties: + body: + type: string + description: | + File source (required unless mediaId is set): + - HTTP/HTTPS URL + - Base64 data URI + filename: + type: string + description: File name with extension (required with body, not with mediaId) + mediaId: + type: string + description: WABA media id from uploadMedia (alternative to body) + mediaType: + type: string + enum: + - image + - video + - audio + - document + description: Required when using mediaId + voice: + type: boolean + description: Native WhatsApp voice note (audio/ogg only) + caption: + type: string + maxLength: 1024 + quotedMsgId: + type: string + chatId: + type: string + phone: + type: integer + additionalProperties: true + description: Send file by URL/base64 or by mediaId + responses: + InvalidToken: + description: Invalid or missing authentication token + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + InternalError: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + MessageSent: + description: Message sent successfully + content: + application/json: + schema: + $ref: "#/components/schemas/MessageSentResponse" + BadRequest: + description: Invalid request parameters + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + TooManyRequests: + description: Rate limit exceeded + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + Success: + description: Operation successful + content: + application/json: + schema: + $ref: "#/components/schemas/SuccessResponse" + securitySchemes: + tokenAuth: + type: apiKey + in: query + name: token + description: JWT token or API key + requestBodies: {} +security: + - tokenAuth: [] diff --git a/quality/generator/provenance.json b/quality/generator/provenance.json new file mode 100644 index 0000000..078f5d1 --- /dev/null +++ b/quality/generator/provenance.json @@ -0,0 +1,111 @@ +{ + "sourceRepository": "https://github.com/1msg/1msg-api", + "sourceCommit": "888ee40a635ab19dbff4c101b8d4329790c7254f", + "generatorVersion": "7.22.0", + "generatorSha256": "3f1e6ce5c6ad4f15242c6170ab43aad4bad771622617eeece4a7d4f72ffaf329", + "contractSha256": "b26aa068f67c892144f2f9f2d9d0ce7a39bb12ff8ad01fd52e68f950fcc2f18c", + "sourceFiles": { + "gen/bundle-public.js": "9b772bb8d1801ef986d8a0a6b0324a8e867fbb961b56549f812e7414223bdf68", + "public/openapi.yaml": "5010623f5ab269355b2b6dc11d9b4d47d1e392d9cb366cef66cbbd8239c3ab2b", + "public/paths/calling/initiateCall.yaml": "82febe8ab1ff895e78a522ef23f943507fa6978a3867f4f7d2b4cdfa07109b77", + "public/paths/calling/callingSettings.yaml": "8f1ff6184a953c190d025f43bdd5ecc85fea2d8e30423b3cf0d381d539c41cf7", + "public/paths/groups/groups_groupId_invite_link.yaml": "8ddcc592bbb8f47e099e11f4d7cc75b91ff79296eaa35130308a1e29c1794836", + "public/paths/groups/groups.yaml": "f595e2f5e147f44a7278fe928154e9a8401a8e28d4760963ae5c2b8e197bf6f4", + "public/paths/groups/groups_groupId.yaml": "48f002f2b8f64e5dca4abfd634c5eb9e2cde2498fe32e44ca1dbc3e3f4fc19f5", + "public/paths/channel/settings.yaml": "e2893c6ce65399cb1b2f260dc9c67b062b3cc1af50213af3cd9deab77205f261", + "public/paths/channel/commerce.yaml": "6b43175a6d9ba928c4cc65033a5933d4d2f2a7f65471dafa07112d058dbb2e1f", + "public/paths/channel/status.yaml": "9670a21034685cdd79fb28b92dbec38feb74f8255df5bbf76a02d23ea0834841", + "public/paths/channel/mmLiteStatus.yaml": "3cb8b6f1dfbc8b028408cf792268af1984ee38e4362e71d809e3ec438cb26d38", + "public/paths/channel/conversationalAutomation.yaml": "2509e0ae34e8d19bc83dc47bb4da584a4a3653f7c7391c17a372e09fe1a68d4c", + "public/paths/channel/whatsappBusinessEncryption.yaml": "62bd8c98b8a3970d9b2a8afbde9a24197cf3e3ab3d075c633330e67c58480fd1", + "public/paths/profile/me.yaml": "c1dc83a56b3d843cd0cced9345904bea65413c3a6b50445baa5d0c7efe2958c2", + "public/paths/users/blockUser.yaml": "334c133da8bac1f3c1ce2c1642b526ad1bd44b2cbd0a91430a36cc0cc57dbdfb", + "public/paths/users/blockedUsers.yaml": "6f86fa8a90fac5e423cb0e267b13503466c42d669c5d77c1f2b0dd43920ab201", + "public/paths/users/unblockUser.yaml": "84fa3721451121872fee2ee7505c94b603a3af2045ca6f774208d25f151c28d1", + "public/paths/templates/templates.yaml": "f6e41fb5b90dca2a80c4eac4d052c51e6846919b95eee37f401b35a642cdfda6", + "public/paths/templates/removeTemplate.yaml": "9c5cfe2f8d51e488cfc957c4bc7c4fb9a8599caf0ec37c53e61891f8eccfc303", + "public/paths/templates/addTemplate.yaml": "a242c132f53885418dba9d718be2690473d3da88955acd705da27b689773e419", + "public/paths/templates/sendTemplate.yaml": "29b4bb0e3793f1f05ec259fb465ceadd942b683da7d09e80faf4156110d2e253", + "public/paths/webhooks/webhook.yaml": "041b37e4a6702de79bb1669ec7ea406e560ca18b7d97aba24d4a4fb0ebbdce72", + "public/paths/messaging/uploadMedia.yaml": "4d7899c88220fcf712667ee3584f93006bd0dcc0de663a54de53409a46f3a9b5", + "public/paths/messaging/sendOrderDetails.yaml": "7e27336c33ca83b575ef6e02445a5196664455f48a55d1755179bb2e36ffc4d9", + "public/paths/messaging/sendMessage.yaml": "5599e639be4dd34414649a7d5faf0d647599cb203d0e95a14f7ae660d6ade6ec", + "public/paths/messaging/sendAddressMessage.yaml": "e759d10dbe389068ff3a0d06e03d93e1616cdbfdc6c2c1c6767270bd31bb6265", + "public/paths/messaging/sendSticker.yaml": "ff7f7b7df63ec75fd5098e3616fd903e92d2ac86bdf3ce6bd08c78450ffabf1b", + "public/paths/messaging/sendContact.yaml": "509e1535bc405bce49a2efaf0fc812293042f338e8c42e26376c06b06e2cf194", + "public/paths/messaging/media-id.yaml": "5afa9603d73d4771348544c80638c46785cc75aa1388808b84fdeb90051e8172", + "public/paths/messaging/sendProduct.yaml": "1871d3e9e4686e76a3ed30f111ea7619cb8c22665e679b6266390014fbb10b9d", + "public/paths/messaging/sendCarousel.yaml": "f188681458f3afdfc9846863a79d5ea28b6756cb1a5a00641bb9a29a332aad70", + "public/paths/messaging/sendLocationRequest.yaml": "b207ed18f773276c39c7bd854ee2c853071bcba244762d5a678e361b7d706446", + "public/paths/messaging/sendReaction.yaml": "65abf2bde681ca8e32b49ec273125f3e4aa41deb3d09873fa87b17e621da946f", + "public/paths/messaging/sendFile.yaml": "6c07d60b36c8a415cab43c61a706aed4a73a53e52a114d50d8fbebc733296530", + "public/paths/messaging/deleteMedia.yaml": "00c09f041df7aa0a49ccdd60122cb6855a89386594676d4fe523bec442c0b4ca", + "public/paths/messaging/messages.yaml": "d99ba398d05acca8ecb817d9b9806b497dea9bbdb079e280c0bcda1bad966b0d", + "public/paths/messaging/retrieveMedia.yaml": "4c73b3992f4653a4b3155be742b6d6720d79a454a17f0f3fbdc9e20d83cc5d01", + "public/paths/messaging/sendPaymentRequest.yaml": "2c83ccf1b20bc0dbae956e2fc445f981cdeac2be742c80b7b4db472f539d8afe", + "public/paths/messaging/sendLocation.yaml": "1f34da40ea89af2a693affffec822fc59663f4efe1554136817dd83fda3ae2ff", + "public/paths/messaging/sendButton.yaml": "5fc74240b27f216c65b45da4975b47e675c6ca4e3da7b204c27d67242566dcc3", + "public/paths/messaging/sendList.yaml": "16cfc200444319d4280a801926d31d0acabbe917bea986460f54ad129f91fcd6", + "public/paths/messaging/readMessage.yaml": "f35a04e3e6a8b115b208f0699ff67503a996284f417cea42efe8d51fc323cb36", + "public/paths/messaging/sendCtaUrl.yaml": "2935101dab612c8289aed85fed0e4900e6eb7a67ef1404000d115ecfefdc23f2", + "public/paths/messaging/sendFlow.yaml": "6add518a4650aa1d507aebe2053cfb53559d6c313fea0f2e1f7c04d4582ebd76", + "public/paths/flows/flows_flowId_preview.yaml": "a838105c68ffaa7b93c2d78d0ddeba7c5a325fef5fe2e7699b115139bd4e422c", + "public/paths/flows/flows_flowId_assets.yaml": "637f3d977b840dc2c804585234f173fb917735fc77f879f785011cbc06c81cf1", + "public/paths/flows/flows_flowId_publish.yaml": "b39e31eafb8a64c4ae74bd3de0aa915ece2f854fa654dc2810cc29ea51586af8", + "public/paths/flows/flows.yaml": "9e5a75c523d7c149412c9e9d4863b7f5dfc3240eb443c1498e420c02eaef2e7f", + "public/paths/flows/flows_flowId.yaml": "d6a84944203deb528081980f8213291d694a42944e21e6df889dbd9158f8ff8e", + "public/paths/flows/flows_flowId_deprecate.yaml": "f2242d190723ee11ed540d962c82b3e7ad9f3d017f75f5bdbd4d02dc9cb7b65e", + "public/paths/flows/flows_flowId_metadata.yaml": "297b8c9059f17b2a5a61c4bab80b112f51304e0659956ba04fc29565e97cb3ef", + "shared/responses/MessageSent.yaml": "0aca0872bb784a7039ff8be7ef57fc13c91638240624831249eeda7aa95704be", + "shared/responses/Success.yaml": "8c4363edb30103d41fbf7bcb8c9bc97782c76611a2bfcd2dbdd48081a2f4b813", + "shared/responses/TooManyRequests.yaml": "330a80e797769b544ed5c434623ba658d9d68bb66a554c560339d12e4266ce7c", + "shared/responses/InvalidToken.yaml": "be7e20df9dfa90f128897053856f768a676acc685906a37445bfc625df0854af", + "shared/responses/InternalError.yaml": "0b7bee775301c9c6aca8f8c665214a08b3e856b5c5eeeb57c036ef22b48f2407", + "shared/responses/BadRequest.yaml": "ef65a9cde8edb7870bf4bf28b6595b1e37f0dd3520faa877f71b6d3d732865d4", + "shared/schemas/ErrorResponse.yaml": "2e2745599a88a23228168b585743e25c6a04f971e4aa319198539a9af0006503", + "shared/schemas/WebhookInfo.yaml": "22a6eecc886d3212ff55c7fb36e5a2f7ae7084666b0e7df79ebb308189afe3b8", + "shared/schemas/FlowInfo.yaml": "9a9f9d8b67420768fdb95170af04eea56f86a729fe6e4b8e55ffe6bb866a7ebb", + "shared/schemas/InitiateCallRequest.yaml": "f3300ec43cd4074afaf38f4e9d3695251ae1e1c1855fe5babb0253b23cb8b30b", + "shared/schemas/ChannelStatusDetailed.yaml": "63cf817f0ae8bda5850b64e2ca24a3cf5867e5e1fe1383ca30ad16cc1d0fe385", + "shared/schemas/SuccessResponse.yaml": "7dbb8695c11212213b86d6b8ddd0d6453e5630891735db447273014b3ae0b53c", + "shared/schemas/ProfileInfo.yaml": "4efee312cd1a86f6bfd06655d800cb156fad93d038f3b49fc52d296be2995207", + "shared/schemas/CallingSettings.yaml": "def3877472b9ed0bacfe9666d22c9bce30bc6b29d7433528ed4bffd471a9c5f5", + "shared/schemas/ConversationalAutomation.yaml": "b918c15cdb9cebb64075fd93c680c6dec48e713245d36535c0c5b423e1522602", + "shared/schemas/WebhookUrlStatus.yaml": "3e52f8356f7795966f9ef68d36c0b7c3d88ee00ad7abac58f9e4b85a46ddc766", + "shared/schemas/StatisticsResponse.yaml": "bd908f2527dc7bfcb41bda15a5748b26210f2bc09a9fa0f24fea7498c9342715", + "shared/schemas/InitiateCallResponse.yaml": "eeb204a62e2b611327a510cc9f714b5fcdf4914bf96f44a7691d050bba48b5aa", + "shared/schemas/MessageSentResponse.yaml": "2dfb0257aafe1d9a7a3697088323909ebd74ac3f61520770e227e5f7b34aad43", + "shared/schemas/ChannelStatus.yaml": "7a95f1b01d56390f14005349b59c76d576f179b581587628f057ff5d6e60bb35", + "shared/parameters/flowId.yaml": "5553440629ce9916e4191be9d67c5b115914cf7bc53eba5f9753fe2233e459c5", + "shared/parameters/date.yaml": "9db4dd8c143a365d139971a4208745646284da7c59f30320cd1958e27685b32a", + "shared/parameters/group_id.yaml": "72da018aeabdf5e1f02c07405b98ece7b59f01152dfff38d026bd565c62486ee", + "shared/parameters/template_external_id.yaml": "2f3d2bf4340c7e47e171e28068ed0a9cf956c48f3b84ca79b6f6fa23f1cfe76b", + "shared/schemas/requests/SendContactRequest.yaml": "02b7a66eb96273183d73ff8934d5a7ae3fdcda891bb7868b1f4b5473c8cae569", + "shared/schemas/requests/ReadMessageRequest.yaml": "e1cbb41a16912c5f0c857af5046708804e7efb0b460b09df22b41c0959dd7666", + "shared/schemas/requests/SendCarouselRequest.yaml": "ddaa58dbb472eea48d291add1354a47e8fce4fc2d6a92641cf5175889cf5eb03", + "shared/schemas/requests/SendReactionRequest.yaml": "d06f0d5f96b9d7d2b6f7ad36ccf5288d0933458ffcf9ba711e02246108f8f9fc", + "shared/schemas/requests/SendFileRequest.yaml": "c47eca1d90b15997ce9b1e955694e8437ca66a141df62dce997921bdf84c2b00", + "shared/schemas/requests/SendLocationRequest.yaml": "9a3a90dd091093680a45dc5ee0b260f48f8b2822e7c74cbc3a6007135bdd05e2", + "shared/schemas/requests/SendProductRequest.yaml": "f6748ab368cf809b4ce9d0fe253466e46a2501390fe4fbc129fe235a1b51fc66", + "shared/schemas/requests/SendButtonRequest.yaml": "d23b38055c5d512bb892706ae323ffeb7454c60d6ad0e78692908bf8c4b38702", + "shared/schemas/requests/SendListRequest.yaml": "5de384d53c241635aa22fbd397db4b2007d2fb2ad04ed32f53cc845117d46b83", + "shared/schemas/requests/SendMessageRequest.yaml": "ee9cd3b8fa8392cabba39ed7307aa9fa93b9485350549f66695560c39eea4b16", + "shared/schemas/requests/UploadMediaRequest.yaml": "ad2639c9bb58140b3b8bc7e05324aa9fb6f661ebada33a327ffc1b856b64dec3" + }, + "language": "php", + "generatorName": "php", + "additionalProperties": { + "composerPackageName": "1msg/sdk", + "invokerPackage": "OneMsgSdk", + "packageName": "Sdk", + "artifactVersion": "1.1.1", + "artifactUrl": "https://github.com/1msg/1msg-sdk-php", + "developerOrganization": "1MSG", + "developerOrganizationUrl": "https://1msg.io", + "licenseName": "MIT" + }, + "roots": [ + "lib/" + ], + "postprocess": true +} diff --git a/quality/generator/verify-negative.mjs b/quality/generator/verify-negative.mjs new file mode 100644 index 0000000..24a22d5 --- /dev/null +++ b/quality/generator/verify-negative.mjs @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { readFileSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +const root = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); +const inventory = JSON.parse( + readFileSync(join(root, "quality/generator/inventory.json"), "utf8"), +); +const path = Object.keys(inventory)[0]; +assert.ok(path, "generation inventory cannot be empty"); +const original = readFileSync(join(root, path)); +const ignored = join( + dirname(path), + `.quality-untracked${path.slice(path.lastIndexOf("."))}`, +); +const ignorePath = join(root, ".gitignore"); +const originalIgnore = readFileSync(ignorePath); +/** Require the real generation replay to fail specifically on output drift, not a missing tool or configuration. */ +function rejectDrift() { + const result = spawnSync( + process.execPath, + ["quality/generator/generate.mjs"], + { cwd: root, encoding: "utf8", timeout: 120000 }, + ); + assert.notEqual(result.status, 0, "generation accepted modified output"); + assert.match( + result.stderr + result.stdout, + /Generated drift/, + "fixture failed for an unrelated reason", + ); +} +try { + writeFileSync( + join(root, path), + Buffer.concat([ + original, + Buffer.from("\n// negative output drift fixture\n"), + ]), + ); + rejectDrift(); + rmSync(join(root, path)); + rejectDrift(); + writeFileSync(join(root, path), original); + writeFileSync( + ignorePath, + Buffer.concat([originalIgnore, Buffer.from(`\n/${ignored}\n`)]), + ); + writeFileSync(join(root, ignored), "unexpected ignored generated output"); + rejectDrift(); + console.log( + "Real regeneration rejects changed, deleted and ignored/untracked output", + ); +} finally { + writeFileSync(join(root, path), original); + writeFileSync(ignorePath, originalIgnore); + rmSync(join(root, ignored), { force: true }); +} diff --git a/quality/postprocess.mjs b/quality/postprocess.mjs new file mode 100644 index 0000000..1300d5c --- /dev/null +++ b/quality/postprocess.mjs @@ -0,0 +1,22 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * Preserve the public sendLocationRequest operation while disambiguating the + * sendLocation request-builder helper emitted by OpenAPI Generator 7.22. + * The original output declares the method twice and PHP cannot load the class. + */ +const path = process.argv[2] + ? resolve(process.argv[2], "lib/Api/MessagingApi.php") + : fileURLToPath(new URL("../lib/Api/MessagingApi.php", import.meta.url)); +const before = await readFile(path, "utf8"); +const anchor = "sendLocationRequest($token, $lat, $lng,"; +const replacement = "sendLocationHttpRequest($token, $lat, $lng,"; +if (!before.includes(replacement)) { + if (before.split(anchor).length !== 4) + throw new Error( + "Unexpected PHP generator layout; review request-builder collision", + ); + await writeFile(path, before.replaceAll(anchor, replacement)); +} diff --git a/tools/quality/README.md b/tools/quality/README.md new file mode 100644 index 0000000..ebb38d7 --- /dev/null +++ b/tools/quality/README.md @@ -0,0 +1,85 @@ +# 1MSG quality template 1.0.2 + +Vendored, private template for 1MSG-156. Install its exact tools with +`npm --prefix tools/quality ci` on Node 24.19.0. The lockfile and +`template-manifest.json` identify the reviewed version. This package is not +published to npm. Each consumer commits a full copy, its manifest, and its own +`quality.config.mjs` inventory. A version update requires policy review and +real negative fixtures; do not edit/reseal one consumer to hide a violation. + +## Profiles and commands + +A (Node runtime) and B (UI) require full static and typed ESLint, strict production, +test and tooling typechecks, architecture, debt gates, tests, and production build. +D applies these to authored TypeScript/tooling while each generated language uses +reproducible generation and native build/offline contract tests. C runs formatting +and static lint on every changed/new authored file and ratchets existing debt. +Compiler presets are provided for NodeNext, Next bundler and Svelte bundler; +consumers must include real sources/tests/tooling and the framework's generated types. + +Exported `staticConfig`, `typedConfig`, `maintainabilityConfig` compose with the +actual framework preset. Next uses its compatible core-web-vitals + TypeScript +presets and React Hooks; Svelte uses its parser/plugin and `svelte-check` on real +components. Browser globals and Node runtime checks have separate scopes. + +`node tools/quality/run.mjs guards` checks source inventory, suppressions, vendored +integrity and documentation for changed functions/private methods/substantive +callbacks plus full pilot roots. Comments explain purpose, effects and constraints; +AST coverage is mechanical, usefulness and accuracy require review. Formatting-only +changes do not create a method change. Meaningless boilerplate does not satisfy review. + +Mandatory graph/debt commands: `unused-production`, `unused-full`, `duplicates`. +`duplicates-full` saves the extended tests/fixtures report and fails analyzer errors; +it is a report, not the runtime clone gate. Configure `duplicatePaths`, +`duplicateFullPaths`, exact generated ignores, explicit formats, and an inventoried +numeric `duplicateMaxPercentage`. Detection uses minLines=5, minTokens=50, mild mode. +Clone groups cannot grow even if the overall duplicate percentage falls. + +Wrap actual JSON analyzer output with: + +- `report --kind eslint --name complexity -- node tools/quality/node_modules/eslint/bin/eslint.js . -c eslint.complexity.config.mjs -f json` +- `report --kind depcruiser --name architecture -- node tools/quality/node_modules/dependency-cruiser/bin/dependency-cruiser.mjs src --config dependency-cruiser.config.cjs --output-type json` + +Only maintainability metrics and existing runtime cycles are baselinable through +those report commands. Other static/typed/architecture diagnostics remain fatal. +Knip keeps separate production and full graphs; missing resolvers, empty graphs, +stale exclusions and malformed output are failures. Configure public exports and +dynamic entrypoints explicitly, never declare all source as entrypoints. + +## Historical debt + +Initial bootstrap is local only and requires owner, expiry, reason and issue: +`--bootstrap --owner TEAM --expires YYYY-MM-DD --reason TEXT --issue URL`. +It reruns the actual analyzer against the trusted Git base using the adopted +configuration. New files, disconnected imports and increased findings cannot be +blessed as historical debt. Replay removes only newly added entry/project patterns +that have no historical source; existing source always comes from Git archive. +Dependencies are shared read-only with the current installation for resolution. + +CI supplies the trusted PR base SHA through `QUALITY_BASE` and fetches full history. +After adoption, allowances cannot grow or renew their expiry; reductions and removed +findings must retire allowances. Reports include head/base SHAs and gate outcome. +Never describe a nonempty baseline as a clean codebase. + +## CI and local hooks + +Run `npm --prefix tools/quality test` as a mandatory independent check. Fixtures +execute actual Knip, jscpd and dependency-cruiser CLIs and verify ratchet growth, +coverage, documentation and malformed reports. Consumers add real typed/framework, +public API, native I/O, SQL, compatibility and generated-drift fixtures as applicable. +`Quality` / `quality-required` must aggregate all applicable checks and fail on any +missing, failed or cancelled required job. Configure this actual job context in +branch protection without removing existing requirements. CI never runs `--fix`. + +The pinned Husky/lint-staged dependencies support committed `.husky/pre-commit` and +an explicit repository `hooks:install` command. Keep hooks quick; CI is authoritative. +Do not change shared Git hook configuration of another worktree during verification. + +## Updating this template + +Change the template at its source, update its version, test actual consumers, +run `node seal.mjs`, copy the complete template, and commit lockfiles and inventory. +Integrity verification prevents hiding authored code under the vendor exemption. +No analyzer flag may depend on CI or a skip-type-aware environment variable. + +Version 1.0.2 resolves Svelte embedded clone paths to their physical source while retaining fragment identity. Initial Knip replay omits only the exact current-only sealed vendor ignore; authored stale ignores remain errors. diff --git a/tools/quality/eslint.mjs b/tools/quality/eslint.mjs new file mode 100644 index 0000000..142b9f1 --- /dev/null +++ b/tools/quality/eslint.mjs @@ -0,0 +1,137 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import imports from 'eslint-plugin-import'; +import node from 'eslint-plugin-n'; +import unicorn from 'eslint-plugin-unicorn'; +import perfectionist from 'eslint-plugin-perfectionist'; +import sonarjs from 'eslint-plugin-sonarjs'; +import globals from 'globals'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +export { + tseslint, + imports as importPlugin, + node as nodePlugin, + sonarjs, + unicorn, + perfectionist, + globals, +}; + +export const sourceFiles = ['**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}']; +export const testFiles = [ + '**/*.{test,spec,stories}.{js,jsx,ts,tsx}', + '**/{test,tests,__tests__,fixtures,mocks}/**', +]; + +/** Return the shared correctness/style layer; callers provide explicit browser/Node globals and generated paths. */ +export function staticConfig({ files = sourceFiles, globals = {} } = {}) { + return [ + { + files, + languageOptions: { globals }, + linterOptions: { reportUnusedDisableDirectives: 'error' }, + plugins: { import: imports, n: node, unicorn, perfectionist, sonarjs }, + settings: { + 'import/resolver': { + [require.resolve('eslint-import-resolver-typescript')]: { + alwaysTryTypes: true, + }, + node: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'] }, + }, + }, + rules: { + ...js.configs.recommended.rules, + eqeqeq: ['error', 'always'], + 'no-var': 'error', + 'prefer-const': 'error', + 'import/no-unresolved': 'error', + 'import/no-duplicates': 'error', + 'n/no-unsupported-features/node-builtins': [ + 'error', + { version: '>=24.0.0 <25.0.0' }, + ], + 'unicorn/prefer-node-protocol': 'error', + 'unicorn/prefer-type-error': 'error', + 'perfectionist/sort-imports': [ + 'error', + { type: 'natural', order: 'asc', sortSideEffects: false }, + ], + 'perfectionist/sort-exports': [ + 'error', + { type: 'natural', order: 'asc' }, + ], + 'sonarjs/no-identical-functions': 'error', + 'sonarjs/no-all-duplicated-branches': 'error', + }, + }, + ...tseslint.configs.recommended.map((config) => ({ + ...config, + files: ['**/*.{ts,tsx,cts,mts}'], + })), + ]; +} + +/** Apply mandatory type-aware Promise and unknown-boundary checks using actual repository tsconfigs. */ +export function typedConfig({ + files = ['**/*.{ts,tsx,cts,mts}'], + project, + tsconfigRootDir, +} = {}) { + return [ + { + files, + languageOptions: { + parser: tseslint.parser, + parserOptions: { project, tsconfigRootDir }, + }, + plugins: { '@typescript-eslint': tseslint.plugin }, + rules: { + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unsafe-assignment': 'error', + '@typescript-eslint/no-unsafe-argument': 'error', + '@typescript-eslint/no-unsafe-call': 'error', + '@typescript-eslint/no-unsafe-member-access': 'error', + '@typescript-eslint/no-unsafe-return': 'error', + }, + }, + ]; +} + +/** Keep complexity diagnostics separate so only measured maintainability debt can use a ratchet. */ +export function maintainabilityConfig({ files = sourceFiles } = {}) { + return [ + { + files, + ignores: testFiles, + plugins: { sonarjs }, + rules: { + 'max-lines': [ + 'error', + { max: 500, skipBlankLines: true, skipComments: true }, + ], + 'max-lines-per-function': [ + 'error', + { max: 80, skipBlankLines: true, skipComments: true }, + ], + complexity: ['error', { max: 20, variant: 'classic' }], + 'sonarjs/cognitive-complexity': ['error', 15], + 'max-depth': ['error', 4], + 'max-params': ['error', 5], + }, + }, + { + files: ['**/*.{jsx,tsx}'], + ignores: testFiles, + rules: { + 'max-lines-per-function': [ + 'error', + { max: 150, skipBlankLines: true, skipComments: true }, + ], + }, + }, + ]; +} diff --git a/tools/quality/lib/baseline.mjs b/tools/quality/lib/baseline.mjs new file mode 100644 index 0000000..b4e11a4 --- /dev/null +++ b/tools/quality/lib/baseline.mjs @@ -0,0 +1,82 @@ +import { createHash } from 'node:crypto'; + +/** Hash normalized evidence so line moves cannot grant new duplicate allowances. */ +export function digest(text) { + return createHash('sha256').update(text).digest('hex'); +} + +/** Keep a stable identifier for each independent finding, rather than a global failure count. */ +export function record(key, value = 1, details = {}) { + if (!key || !Number.isFinite(value) || value < 0) + throw new Error('Invalid analyzer finding'); + return { key, value, ...details }; +} + +/** Reject duplicate diagnostic identities because collapsing them could hide growing debt. */ +export function index(records) { + const result = new Map(); + for (const entry of records) { + if (result.has(entry.key)) + throw new Error(`Duplicate finding identity: ${entry.key}`); + result.set(entry.key, entry); + } + return result; +} + +/** Compare current findings with allowances and the trusted base's allowances, including stale debt. */ +export function assess( + findings, + baseline, + previous, + today = new Date().toISOString().slice(0, 10), +) { + const errors = []; + if (baseline.schemaVersion !== 1 || !Array.isArray(baseline.entries)) { + throw new Error('Unsupported or missing baseline schema'); + } + const current = index(findings); + const allowances = index(baseline.entries); + const older = previous ? index(previous.entries) : null; + for (const [key, allowance] of allowances) { + if ( + !allowance.owner || + !allowance.reason || + !allowance.issue || + !allowance.expires + ) { + errors.push(`Unowned/unexplained allowance: ${key}`); + } + if ( + !/^\d{4}-\d{2}-\d{2}$/.test(allowance.expires ?? '') || + allowance.expires < today + ) { + errors.push(`Expired or invalid allowance: ${key}`); + } + const finding = current.get(key); + if (!finding) errors.push(`Remove stale allowance: ${key}`); + else if (finding.value < allowance.value) + errors.push(`Reduce allowance to ${finding.value}: ${key}`); + if (older) { + const old = older.get(key); + if (!old || allowance.value > old.value) + errors.push(`Baseline expanded relative to trusted base: ${key}`); + if (old && allowance.expires > old.expires) + errors.push(`Allowance expiry extended: ${key}`); + } + } + for (const [key, finding] of current) { + if (!allowances.has(key) || finding.value > allowances.get(key).value) { + errors.push(`New or increased violation (${finding.value}): ${key}`); + } + } + return errors; +} + +/** Parse machine-readable output only; a missing report or unexpected schema is an analysis failure. */ +export function parseJson(stdout, analyzer) { + try { + return JSON.parse(stdout); + } catch { + throw new Error(`${analyzer} did not return a complete JSON report`); + } +} diff --git a/tools/quality/lib/git.mjs b/tools/quality/lib/git.mjs new file mode 100644 index 0000000..2aa6dcb --- /dev/null +++ b/tools/quality/lib/git.mjs @@ -0,0 +1,86 @@ +import { spawnSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; + +/** Run a bounded, non-shell child process; preserve failures instead of returning an empty analysis. */ +export function execute(command, args = [], options = {}) { + const result = spawnSync(command, args, { + encoding: 'utf8', + maxBuffer: 128 * 1024 * 1024, + timeout: 600_000, + ...options, + }); + if (result.error || result.signal || result.status === null) { + throw new Error( + `${command} did not complete: ${result.error?.message ?? result.signal}`, + ); + } + return result; +} + +/** Execute read-only Git operations and reject a missing ref or failed comparison. */ +export function git(args) { + const result = execute('git', args); + if (result.status !== 0) + throw new Error(`git ${args[0]} failed: ${result.stderr.trim()}`); + return result.stdout; +} + +/** Resolve one trusted comparison commit; CI explicitly supplies its PR base SHA. */ +export function baseRef( + defaultBranch = 'main', + explicit = process.env.QUALITY_BASE, +) { + if (explicit) + return git(['rev-parse', '--verify', `${explicit}^{commit}`]).trim(); + return git(['merge-base', 'HEAD', `origin/${defaultBranch}`]).trim(); +} + +/** Return tracked paths, including staged additions, with unambiguous NUL delimiters. */ +export function trackedFiles() { + return git(['ls-files', '-z']).split('\0').filter(Boolean); +} + +/** Include committed, staged, unstaged and untracked additions in local changed-file checks. */ +export function changedFiles(base) { + return [ + ...new Set( + [ + ...git([ + 'diff', + '--name-only', + '--diff-filter=ACMR', + '-z', + base, + '--', + ]).split('\0'), + ...git(['ls-files', '--others', '--exclude-standard', '-z']).split( + '\0', + ), + ].filter(Boolean), + ), + ].sort(); +} + +/** Read an optional file at the verified base; missing files mean additions, not failed Git. */ +export function baseFile(base, path) { + const paths = git([ + 'ls-tree', + '-r', + '--name-only', + '-z', + base, + '--', + path, + ]).split('\0'); + return paths.includes(path) ? git(['show', `${base}:${path}`]) : null; +} + +/** Read current bytes only when the path exists; deleted files have no current diagnostics. */ +export function currentFile(path) { + try { + return readFileSync(path, 'utf8'); + } catch (error) { + if (error.code === 'ENOENT') return null; + throw error; + } +} diff --git a/tools/quality/lib/guards.mjs b/tools/quality/lib/guards.mjs new file mode 100644 index 0000000..500c8fe --- /dev/null +++ b/tools/quality/lib/guards.mjs @@ -0,0 +1,356 @@ +import ts from 'typescript'; +import picomatch from 'picomatch'; +import { existsSync } from 'node:fs'; +import { baseFile, changedFiles, currentFile, trackedFiles } from './git.mjs'; +import { digest } from './baseline.mjs'; +import { verifyTemplate } from './template.mjs'; + +const sourcePattern = /\.(?:[cm]?[jt]sx?|svelte)$/; + +/** Match repo-relative paths consistently; generated and exception paths stay explicit. */ +export function matches(path, patterns = []) { + return patterns.some((pattern) => picomatch(pattern, { dot: true })(path)); +} + +/** Validate manifest entries before they can remove any source from a mandatory check. */ +export function validateConfig( + config, + today = new Date().toISOString().slice(0, 10), +) { + if ( + config.schemaVersion !== 1 || + !['A', 'B', 'C', 'D'].includes(config.profile) + ) { + throw new Error( + 'quality.config.mjs requires schemaVersion=1 and a concrete profile', + ); + } + if (!Array.isArray(config.sourceRoots) || config.sourceRoots.length === 0) { + throw new Error( + 'sourceRoots must inventory the authored source and tooling', + ); + } + for (const root of config.sourceRoots) { + if (!existsSync(root)) throw new Error(`Missing source root: ${root}`); + } + for (const entry of config.generated ?? []) { + if ( + !entry.producer || + !entry.pattern || + ['**', '**/*', '**/*.ts', '**/*.js'].includes(entry.pattern) + ) { + throw new Error( + 'Generated exclusions require a narrow path and actual producer', + ); + } + } + for (const entry of [ + ...(config.excluded ?? []), + ...(config.comments?.exclude ?? []), + ]) { + if ( + !entry.pattern || + !entry.owner || + !entry.reason || + !entry.expires || + entry.expires < today + ) { + throw new Error(`Unowned or expired source exception: ${entry.pattern}`); + } + } +} + +/** Parse script content with its actual syntax kind; Svelte markup is handled by framework checks. */ +export function parseUnits(path, text) { + if (path.endsWith('.svelte')) { + return [...text.matchAll(/]*)>([\s\S]*?)<\/script\s*>/g)].map( + (match, index) => ({ + suffix: `script${index}`, + ast: ts.createSourceFile( + path, + match[2], + ts.ScriptTarget.Latest, + true, + /lang\s*=\s*["']ts["']/.test(match[1]) + ? ts.ScriptKind.TS + : ts.ScriptKind.JS, + ), + }), + ); + } + const kind = path.endsWith('.tsx') + ? ts.ScriptKind.TSX + : path.endsWith('.jsx') + ? ts.ScriptKind.JSX + : /\.[cm]?ts$/.test(path) + ? ts.ScriptKind.TS + : ts.ScriptKind.JS; + return [ + { + suffix: '', + ast: ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, kind), + }, + ]; +} + +/** Compare semantic tokens so formatting/quote changes do not create a false method edit. */ +function functionFingerprint(fragment) { + const scanner = ts.createScanner( + ts.ScriptTarget.Latest, + true, + ts.LanguageVariant.JSX, + fragment, + ); + const tokens = []; + for ( + let token = scanner.scan(); + token !== ts.SyntaxKind.EndOfFileToken; + token = scanner.scan() + ) { + const value = [ + ts.SyntaxKind.StringLiteral, + ts.SyntaxKind.NoSubstitutionTemplateLiteral, + ].includes(token) + ? scanner.getTokenValue() + : scanner.getTokenText(); + tokens.push([token, value]); + } + const meaningful = tokens.filter(([kind], index) => { + if (kind !== ts.SyntaxKind.CommaToken) return true; + const next = tokens[index + 1]?.[0]; + if (next === ts.SyntaxKind.CloseBracketToken) { + // Array elisions change length and must never be treated as formatting. + const previous = tokens[index - 1]?.[0]; + return [ + ts.SyntaxKind.CommaToken, + ts.SyntaxKind.OpenBracketToken, + ].includes(previous); + } + return ![ + ts.SyntaxKind.CloseParenToken, + ts.SyntaxKind.CloseBraceToken, + ].includes(next); + }); + return digest(JSON.stringify(meaningful)); +} + +/** Identify callbacks by their lexical owner, not their position among unrelated functions. */ +function lexicalScope(node, ast) { + const owners = []; + for (let current = node.parent; current; current = current.parent) { + if (ts.isFunctionLike(current) || ts.isClassLike(current)) { + const name = current.name ?? current.parent?.name; + if (name) owners.push(name.getText(ast)); + } else if (ts.isCallExpression(current)) { + owners.push(current.expression.getText(ast)); + } + } + return owners.reverse().join('/'); +} + +/** Resolve a function's meaningful enclosing declaration for stable identity and adjacent documentation. */ +function declaration(node) { + let owner = node; + if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) { + if (ts.isVariableDeclaration(node.parent)) { + owner = node.parent.parent.parent; + } else if ( + ts.isPropertyAssignment(node.parent) || + ts.isPropertyDeclaration(node.parent) + ) { + owner = node.parent; + } + } + return owner; +} + +/** Read adjacent callback docs through casts/assignments, stopping at its own statement or enclosing function. */ +function callbackComments(node, ast) { + const comments = []; + let current = node; + while (current) { + if (current !== node && ts.isFunctionLike(current)) break; + const trivia = ast.text.slice( + current.getFullStart(), + current.getStart(ast), + ); + comments.push(...(trivia.match(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g) ?? [])); + if (ts.isStatement(current)) break; + current = current.parent; + } + return comments; +} + +/** Extract all implemented functions, including private methods, factories and substantive callbacks. */ +export function functionsIn(path, text) { + const functions = []; + for (const { suffix, ast } of parseUnits(path, text)) { + if (ast.parseDiagnostics.length) + throw new Error( + `Cannot parse ${path}: ${ast.parseDiagnostics[0].messageText}`, + ); + const visit = (node) => { + if (ts.isFunctionLike(node) && node.body) { + const owner = declaration(node); + const name = + node.name?.getText(ast) ?? + node.parent.name?.getText(ast) ?? + ''; + const ranges = + ts.getLeadingCommentRanges(ast.text, owner.getFullStart()) ?? []; + const comments = ranges.map((range) => + ast.text.slice(range.pos, range.end), + ); + const parentComments = + node.parent && ts.isCallExpression(node.parent) + ? ( + ts.getLeadingCommentRanges( + ast.text, + node.parent.parent.getFullStart(), + ) ?? [] + ).map((range) => ast.text.slice(range.pos, range.end)) + : []; + functions.push({ + key: `${suffix}:${lexicalScope(node, ast)}:${name}`, + signature: functionFingerprint(node.getText(ast)), + name, + comments: [ + ...comments, + ...parentComments, + ...(name === '' ? callbackComments(node, ast) : []), + ], + line: ast.getLineAndCharacterOfPosition(node.getStart(ast)).line + 1, + callback: name === '', + // An expression-only projection has no independent scenario; its enclosing function is documented. + trivialCallback: + name === '' && + !ts.isBlock(node.body) && + node.body.getText(ast).length < 80 && + !/await|throw|fetch|\bnew\b/.test(node.body.getText(ast)), + }); + } + ts.forEachChild(node, visit); + }; + visit(ast); + } + return functions; +} + +/** Identify suppression directives in comments only, never in strings or generated documentation examples. */ +export function suppressionErrors(path, text, config) { + const errors = []; + for (const { ast } of parseUnits(path, text)) { + const scanner = ts.createScanner( + ts.ScriptTarget.Latest, + false, + ts.LanguageVariant.Standard, + ast.text, + ); + for ( + let token = scanner.scan(); + token !== ts.SyntaxKind.EndOfFileToken; + token = scanner.scan() + ) { + if ( + ![ + ts.SyntaxKind.SingleLineCommentTrivia, + ts.SyntaxKind.MultiLineCommentTrivia, + ].includes(token) + ) + continue; + const comment = scanner.getTokenText(); + if ( + /@ts-(?:ignore|nocheck)\b/.test(comment) || + /eslint-disable(?!-next-line|-line)\b/.test(comment) + ) { + errors.push(`${path}: broad lint/type suppression is forbidden`); + } + if ( + /eslint-disable(?:-next-line|-line)\b/.test(comment) && + !/eslint-disable(?:-next-line|-line)\s+[\w@/-]+(?:\s*,\s*[\w@/-]+)*\s+--\s+.{12,}/.test( + comment, + ) + ) { + errors.push( + `${path}: suppression must name rules and explain the scoped exception`, + ); + } + if ( + /@ts-expect-error\b/.test(comment) && + (!/@ts-expect-error\s*:?\s+.{12,}/.test(comment) || + !matches(path, config.negativeTypeTests ?? [])) + ) { + errors.push( + `${path}: @ts-expect-error requires an explained negative type-test scope`, + ); + } + } + } + return errors; +} + +/** Enforce complete authored-file coverage plus documentation on changed functions and pilot trees. */ +export function runGuards(config, base) { + validateConfig(config); + verifyTemplate(base); + const changed = new Set(changedFiles(base)); + const allFiles = [...new Set([...trackedFiles(), ...changed])]; + const generated = (config.generated ?? []).map((entry) => entry.pattern); + const excluded = (config.excluded ?? []).map((entry) => entry.pattern); + const errors = []; + let checked = 0; + for (const path of allFiles.filter((path) => sourcePattern.test(path))) { + if (path.startsWith('tools/quality/')) continue; // Versioned template has its own tests and documented API. + if (matches(path, [...generated, ...excluded])) continue; + if ( + !config.sourceRoots.some( + (root) => + root === '.' || + path === root || + path.startsWith(`${root.replace(/\/$/, '')}/`), + ) + ) { + errors.push(`${path}: authored file has no declared source root`); + continue; + } + const text = currentFile(path); + if (text === null) continue; + checked++; + const full = matches(path, config.comments?.full); + if (!changed.has(path) && !full) continue; + errors.push(...suppressionErrors(path, text, config)); + if ( + matches( + path, + (config.comments?.exclude ?? []).map((entry) => entry.pattern), + ) + ) + continue; + const before = baseFile(base, path); + const previous = new Map(); + for (const fn of before === null ? [] : functionsIn(path, before)) { + const identity = `${fn.key}:${fn.signature}`; + previous.set(identity, (previous.get(identity) ?? 0) + 1); + } + for (const fn of functionsIn(path, text)) { + const identity = `${fn.key}:${fn.signature}`; + const remaining = previous.get(identity) ?? 0; + if (remaining > 0) previous.set(identity, remaining - 1); + if ((!full && remaining > 0) || fn.trivialCallback) continue; + const docs = fn.comments + .filter((comment) => fn.callback || comment.startsWith('/**')) + .join('\n'); + const words = docs + .replace(/\{@[^}]+\}|\{[^}]+\}|\*|\//g, ' ') + .trim() + .split(/\s+/); + if (words.length < 3 || docs.length < 18) + errors.push( + `${path}:${fn.line}: document ${fn.name}'s purpose and contract`, + ); + } + } + if (checked === 0 && config.profile !== 'D') + errors.push('No authored JS/TS/Svelte files were checked'); + return { checked, errors }; +} diff --git a/tools/quality/lib/historical.mjs b/tools/quality/lib/historical.mjs new file mode 100644 index 0000000..7ed6be8 --- /dev/null +++ b/tools/quality/lib/historical.mjs @@ -0,0 +1,139 @@ +import { + cpSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { tmpdir } from 'node:os'; +import { execute, git } from './git.mjs'; +import picomatch from 'picomatch'; + +/** Omit only new entry/project targets that cannot exist in the historical source being measured. */ +function historicalPatterns(value, prefix, currentPaths, basePaths) { + const patterns = typeof value === 'string' ? [value] : value; + if (!Array.isArray(patterns)) return value; + return patterns.filter((pattern) => { + if (typeof pattern !== 'string' || pattern.startsWith('!')) return true; + const match = picomatch( + join(prefix, pattern.replace(/!$/, '')).replaceAll('\\', '/'), + { dot: true }, + ); + return ( + basePaths.some((path) => match(path)) || + !currentPaths.some((path) => match(path)) + ); + }); +} + +/** Keep current Knip policy but remove newly introduced tooling-only roots from historical coverage. */ +function prepareHistoricalKnip(path, currentPaths, basePaths) { + if (!path.endsWith('.json') || !existsSync(path)) return; + const config = JSON.parse(readFileSync(path, 'utf8')); + // The sealed vendor snapshot is new during initial adoption. Its current-only + // import roots are removed below, so this exact exemption would otherwise be stale. + if ( + !basePaths.some((path) => path.startsWith('tools/quality/')) && + Array.isArray(config.ignore) + ) { + config.ignore = config.ignore.filter( + (pattern) => pattern !== 'tools/quality/**', + ); + } + const scopes = [['.', config], ...Object.entries(config.workspaces ?? {})]; + for (const [prefix, scope] of scopes) { + if (!scope || typeof scope !== 'object') continue; + for (const key of ['entry', 'project']) { + if (scope[key]) + scope[key] = historicalPatterns( + scope[key], + prefix, + currentPaths, + basePaths, + ); + } + } + writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`); +} + +/** Re-run the same analyzer against trusted source with the newly adopted analysis configuration. */ +export function historicalAnalysis(base, config, templateDirectory, analyze) { + const current = process.cwd(); + const snapshot = mkdtempSync(join(tmpdir(), '1msg-quality-base-')); + const archiveDirectory = mkdtempSync(join(tmpdir(), '1msg-quality-archive-')); + try { + const archivePath = join(archiveDirectory, 'source.tar'); + const archive = execute('git', [ + 'archive', + '--format=tar', + '--output', + archivePath, + base, + ]); + if (archive.status !== 0) + throw new Error(`Cannot read trusted source: ${archive.stderr}`); + const extract = execute('tar', ['-xf', archivePath, '-C', snapshot]); + if (extract.status !== 0) + throw new Error(`Cannot unpack trusted source: ${extract.stderr}`); + const paths = git(['ls-files', '-co', '--exclude-standard', '-z']) + .split('\0') + .filter(Boolean); + const basePaths = git(['ls-tree', '-r', '--name-only', '-z', base]) + .split('\0') + .filter(Boolean); + // Some legacy multi-package repos adopt their first root manifest with the quality tooling. + // Findings on this new manifest still cannot become debt: initialEvidence requires a base file. + if ( + !existsSync(join(snapshot, 'package.json')) && + existsSync('package.json') + ) { + cpSync('package.json', join(snapshot, 'package.json')); + } + const configuration = paths.filter((path) => + /(?:^|\/)(?:eslint(?:\.[\w-]+)?\.config\.[cm]?js|dependency-cruiser(?:\.[\w-]+)?\.config\.[cm]?js|tsconfig(?:\.[\w-]+)?\.json|quality\.config\.mjs)$/.test( + path, + ), + ); + configuration.push(config.knipConfig ?? 'knip.json'); + for (const path of new Set(configuration)) { + if (!existsSync(path)) continue; + mkdirSync(dirname(join(snapshot, path)), { recursive: true }); + cpSync(path, join(snapshot, path)); + } + prepareHistoricalKnip( + join(snapshot, config.knipConfig ?? 'knip.json'), + paths, + basePaths, + ); + // Dependencies are analyzer inputs; historical application source always comes from Git above. + const packages = new Set([ + '.', + ...paths.filter((path) => path.endsWith('/package.json')).map(dirname), + ]); + for (const path of packages) { + const modules = resolve(current, path, 'node_modules'); + const destination = join(snapshot, path, 'node_modules'); + if (!existsSync(modules) || existsSync(destination)) continue; + mkdirSync(dirname(destination), { recursive: true }); + symlinkSync(modules, destination, 'dir'); + } + const destination = join(snapshot, 'tools/quality'); + rmSync(destination, { recursive: true, force: true }); + mkdirSync(dirname(destination), { recursive: true }); + symlinkSync(templateDirectory, destination, 'dir'); + process.chdir(snapshot); + const historicalConfig = { + ...config, + duplicatePaths: config.duplicatePaths?.filter((path) => existsSync(path)), + }; + return analyze(historicalConfig); + } finally { + process.chdir(current); + rmSync(snapshot, { recursive: true, force: true }); + rmSync(archiveDirectory, { recursive: true, force: true }); + } +} diff --git a/tools/quality/lib/knip-reporter.mjs b/tools/quality/lib/knip-reporter.mjs new file mode 100644 index 0000000..87999ef --- /dev/null +++ b/tools/quality/lib/knip-reporter.mjs @@ -0,0 +1,42 @@ +import jsonReporter from '../node_modules/knip/dist/reporters/json.js'; + +/** Reject incomplete Knip graphs before emitting the pinned version's standard JSON report. */ +export default async function qualityReporter(options) { + if (options.hasConfigLoadErrors || options.counters.processed === 0) { + throw new Error( + 'Knip failed to load configuration or analyzed an empty graph', + ); + } + for (const required of [ + 'files', + 'exports', + 'dependencies', + 'unlisted', + 'unresolved', + ]) { + if (!options.report[required]) + throw new Error(`Mandatory Knip issue type disabled: ${required}`); + } + const incomplete = options.configurationHints.filter((hint) => + [ + 'entry-empty', + 'project-empty', + 'project-extension-unregistered', + 'project-extension-excluded', + 'workspace-unconfigured', + 'top-level-unconfigured', + 'ignore', + 'ignoreFiles', + 'ignoreBinaries', + 'ignoreDependencies', + 'ignoreUnresolved', + 'ignoreWorkspaces', + ].includes(hint.type), + ); + if (incomplete.length) { + throw new Error( + `Knip coverage/stale exclusion failure: ${incomplete.map((hint) => `${hint.type}:${hint.identifier}`).join(', ')}`, + ); + } + await jsonReporter(options); +} diff --git a/tools/quality/lib/reports.mjs b/tools/quality/lib/reports.mjs new file mode 100644 index 0000000..19c9a8e --- /dev/null +++ b/tools/quality/lib/reports.mjs @@ -0,0 +1,265 @@ +import { relative, isAbsolute } from 'node:path'; +import { existsSync } from 'node:fs'; +import ts from 'typescript'; +import { currentFile } from './git.mjs'; +import { digest, record } from './baseline.mjs'; + +/** Convert report paths to portable repository paths; external paths are not silently accepted. */ +export function repoPath(path) { + if (typeof path !== 'string' || !path) + throw new Error('Analyzer returned a missing file path'); + const normalized = (isAbsolute(path) ? relative(process.cwd(), path) : path) + .replaceAll('\\', '/') + .replace(/^\.\//, ''); + if (normalized === '..' || normalized.startsWith('../')) + throw new Error(`Analyzer escaped repository: ${path}`); + return normalized; +} + +/** Preserve all enabled Knip issue categories and distinguish separate symbols in the same file. */ +export function knipRecords(report) { + if (!report || !Array.isArray(report.issues)) + throw new Error('Invalid Knip JSON schema'); + const results = []; + for (const row of report.issues) { + const path = repoPath(row.file); + for (const [type, issues] of Object.entries(row)) { + if (['file', 'owners'].includes(type)) continue; + if (!Array.isArray(issues)) + throw new Error(`Invalid Knip category: ${type}`); + const grouped = new Map(); + for (const issue of issues) { + const symbols = (Array.isArray(issue) ? issue : [issue]).map( + (item) => item.name ?? item.symbol, + ); + if (symbols.some((symbol) => typeof symbol !== 'string')) + throw new Error(`Invalid Knip symbol: ${path}/${type}`); + const symbol = symbols.sort().join(','); + grouped.set(symbol, (grouped.get(symbol) ?? 0) + 1); + } + for (const [symbol, count] of grouped) + results.push( + record(`${path}|${type}|${symbol}`, count, { + path, + rule: type, + symbol, + }), + ); + } + } + return results.sort((a, b) => a.key.localeCompare(b.key)); +} + +/** Normalize token evidence without stripping string values or treating comments as meaningful duplicates. */ +export function tokenFingerprint(fragment) { + const scanner = ts.createScanner( + ts.ScriptTarget.Latest, + true, + ts.LanguageVariant.JSX, + fragment, + ); + const tokens = []; + for ( + let token = scanner.scan(); + token !== ts.SyntaxKind.EndOfFileToken; + token = scanner.scan() + ) { + tokens.push([token, scanner.getTokenText()]); + } + return digest(JSON.stringify(tokens)); +} + +/** Parse actual dependency-cruiser output; only pre-existing runtime cycles may be tracked as debt. */ +export function architectureRecords(report) { + if ( + !Array.isArray(report?.modules) || + !report.modules.length || + !report.summary?.totalCruised || + !Array.isArray(report.summary.violations) + ) + throw new Error('dependency-cruiser returned invalid/empty coverage'); + const cycles = new Map(); + for (const violation of report.summary.violations) { + if ( + violation.type !== 'cycle' || + violation.rule?.name !== 'no-runtime-cycles' || + !Array.isArray(violation.cycle) || + !violation.cycle.length + ) { + throw new Error( + `Non-baselinable architecture violation: ${violation.rule?.name}: ${violation.from} -> ${violation.to}`, + ); + } + const members = violation.cycle.map((member) => repoPath(member.name)); + const rotations = members.map((_, index) => + [...members.slice(index), ...members.slice(0, index)].join(' -> '), + ); + const key = `no-runtime-cycles|${rotations.sort()[0]}`; + cycles.set( + key, + record(key, 1, { + rule: 'no-runtime-cycles', + paths: [...new Set(members)].sort(), + }), + ); + } + return [...cycles.values()].sort((a, b) => a.key.localeCompare(b.key)); +} + +/** Resolve jscpd's embedded Svelte fragments to an existing source while retaining their language identity. */ +function duplicateSource(path) { + const participant = repoPath(path); + const embedded = + /^(.*\.svelte):(html|css|scss|sass|less|stylus|javascript|typescript|jsx|tsx)$/.exec( + participant, + ); + if (!embedded) { + if (/\.svelte:/.test(participant)) + throw new Error(`Unknown Svelte clone fragment: ${participant}`); + return { path: participant, participant, embedded: false }; + } + const source = embedded[1]; + if (!existsSync(source)) + throw new Error(`Missing Svelte clone source: ${source}`); + return { path: source, participant, embedded: true }; +} + +/** Identify clone groups by normalized code and participants, so unrelated unique code cannot dilute violations. */ +export function duplicateRecords(report) { + if ( + !report || + !Array.isArray(report.duplicates) || + !report.statistics?.total?.sources + ) { + throw new Error('jscpd report is missing or has empty source coverage'); + } + const grouped = new Map(); + for (const clone of report.duplicates) { + const first = duplicateSource(clone.firstFile?.name); + const second = duplicateSource(clone.secondFile?.name); + let fragment = clone.fragment; + if (typeof fragment !== 'string') { + if (first.embedded || second.embedded) + throw new Error('jscpd omitted embedded fragment evidence'); + const source = currentFile(first.path); + if ( + source === null || + !Number.isInteger(clone.firstFile.start) || + !Number.isInteger(clone.firstFile.end) + ) { + throw new Error('jscpd omitted clone evidence'); + } + fragment = source + .split(/\r?\n/) + .slice(clone.firstFile.start - 1, clone.firstFile.end) + .join('\n'); + } + const fingerprint = `${clone.format}|${tokenFingerprint(fragment)}`; + const group = grouped.get(fingerprint) ?? { + paths: new Set(), + participants: new Set(), + pairs: 0, + tokens: 0, + }; + group.paths.add(first.path); + group.paths.add(second.path); + group.participants.add(first.participant); + group.participants.add(second.participant); + group.pairs++; + group.tokens = Math.max(group.tokens, clone.tokens ?? 0); + grouped.set(fingerprint, group); + } + return [...grouped] + .map(([fingerprint, group]) => { + const paths = [...group.paths].sort(); + const participants = [...group.participants].sort(); + return record(`${fingerprint}|${participants.join('|')}`, group.pairs, { + paths, + ...(participants.some((path) => /\.svelte:/.test(path)) + ? { participants } + : {}), + tokens: group.tokens, + }); + }) + .sort((a, b) => a.key.localeCompare(b.key)); +} + +/** Locate the enclosing declaration for an ESLint diagnostic without using unstable line numbers as identity. */ +function scopeAt(source, line, column) { + const offset = source.getPositionOfLineAndCharacter( + Math.max(0, line - 1), + Math.max(0, column - 1), + ); + let scope = ''; + const visit = (node) => { + if (node.pos <= offset && node.end >= offset) { + if (ts.isFunctionLike(node)) + scope = + node.name?.getText(source) ?? + node.parent.name?.getText(source) ?? + ''; + ts.forEachChild(node, visit); + } + }; + visit(source); + return scope; +} + +/** Only complexity diagnostics may use a debt baseline; parser, typed and correctness errors remain fatal. */ +export function eslintRecords(report) { + if (!Array.isArray(report) || report.length === 0) + throw new Error('ESLint returned empty source coverage'); + const allowed = new Set([ + 'complexity', + 'max-depth', + 'max-params', + 'max-lines', + 'max-lines-per-function', + 'sonarjs/cognitive-complexity', + ]); + const results = []; + for (const file of report) { + const path = repoPath(file.filePath); + if (!Array.isArray(file.messages)) throw new Error('Invalid ESLint report'); + const source = ts.createSourceFile( + path, + currentFile(path) ?? '', + ts.ScriptTarget.Latest, + true, + ); + const occurrences = new Map(); + for (const message of file.messages) { + if (message.fatal || !allowed.has(message.ruleId)) { + throw new Error( + `${path}: non-baselinable ${message.ruleId ?? 'parser'}: ${message.message}`, + ); + } + const metricPatterns = { + complexity: /complexity of (\d+)/i, + 'max-depth': /(?:nested too deeply|maximum allowed).*?\((\d+)\)/i, + 'max-params': /too many parameters \((\d+)\)/i, + 'max-lines': /too many lines \((\d+)\)/i, + 'max-lines-per-function': /too many lines \((\d+)\)/i, + 'sonarjs/cognitive-complexity': /complexity from (\d+)/i, + }; + const values = metricPatterns[message.ruleId].exec(message.message); + if (!values) + throw new Error(`No metric in ESLint diagnostic: ${message.message}`); + const scope = + message.ruleId === 'max-lines' + ? '' + : scopeAt(source, message.line ?? 1, message.column ?? 1); + const identity = `${path}|${message.ruleId}|${scope}`; + const occurrence = occurrences.get(identity) ?? 0; + occurrences.set(identity, occurrence + 1); + results.push( + record(`${identity}|${occurrence}`, Number(values[1]), { + path, + rule: message.ruleId, + scope, + }), + ); + } + } + return results.sort((a, b) => a.key.localeCompare(b.key)); +} diff --git a/tools/quality/lib/template.mjs b/tools/quality/lib/template.mjs new file mode 100644 index 0000000..0a31562 --- /dev/null +++ b/tools/quality/lib/template.mjs @@ -0,0 +1,56 @@ +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { baseFile } from './git.mjs'; +import { digest } from './baseline.mjs'; + +/** Inventory all vendored files except installed dependencies; unexpected symlinks cannot hide source. */ +export function templateFiles(root, relative = '') { + return readdirSync(join(root, relative), { withFileTypes: true }) + .flatMap((entry) => { + const path = join(relative, entry.name).replaceAll('\\', '/'); + if (path === 'node_modules' || path === 'template-manifest.json') + return []; + if (entry.isSymbolicLink()) + throw new Error(`Untracked template symlink: ${path}`); + return entry.isDirectory() ? templateFiles(root, path) : [path]; + }) + .sort(); +} + +/** Keep the vendored exemption exact and require a new template version for changed preset contents. */ +export function verifyTemplate(base) { + const root = 'tools/quality'; + if (!existsSync(root)) return; // Standalone CLI fixtures exercise the runner outside a vendored checkout. + const manifestPath = `${root}/template-manifest.json`; + if (!existsSync(manifestPath)) + throw new Error( + 'Vendored quality template is missing its versioned inventory', + ); + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); + const packageInfo = JSON.parse(readFileSync(`${root}/package.json`, 'utf8')); + if (manifest.schemaVersion !== 1 || manifest.version !== packageInfo.version) + throw new Error('Template version mismatch'); + const files = templateFiles(root); + if ( + JSON.stringify(files) !== JSON.stringify(Object.keys(manifest.files).sort()) + ) + throw new Error('Quality template has undeclared/missing files'); + for (const path of files) { + if ( + digest(readFileSync(`${root}/${path}`, 'utf8')) !== manifest.files[path] + ) + throw new Error(`Quality template integrity mismatch: ${path}`); + } + const previousText = baseFile(base, manifestPath); + if (previousText) { + const previous = JSON.parse(previousText); + if ( + previous.version === manifest.version && + JSON.stringify(previous.files) !== JSON.stringify(manifest.files) + ) { + throw new Error( + 'Quality template changed without an explicit version update', + ); + } + } +} diff --git a/tools/quality/node_modules b/tools/quality/node_modules new file mode 120000 index 0000000..0c2f3cd --- /dev/null +++ b/tools/quality/node_modules @@ -0,0 +1 @@ +/Users/jannur/WebstormProjects/1msg-quality-156-20261012/shared-quality-template/node_modules \ No newline at end of file diff --git a/tools/quality/package-lock.json b/tools/quality/package-lock.json new file mode 100644 index 0000000..5b96b4b --- /dev/null +++ b/tools/quality/package-lock.json @@ -0,0 +1,6852 @@ +{ + "name": "@1msg/quality-template", + "version": "1.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@1msg/quality-template", + "version": "1.0.2", + "dependencies": { + "@eslint/js": "9.39.5", + "dependency-cruiser": "18.6.0", + "eslint": "9.39.5", + "eslint-import-resolver-typescript": "4.4.5", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-n": "18.4.1", + "eslint-plugin-perfectionist": "5.12.1", + "eslint-plugin-react": "7.37.5", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-sonarjs": "3.0.7", + "eslint-plugin-unicorn": "62.0.0", + "globals": "17.13.0", + "husky": "9.1.7", + "jscpd": "5.4.1", + "knip": "6.41.0", + "lint-staged": "16.2.7", + "picomatch": "4.0.7", + "prettier": "3.9.10", + "typescript": "5.9.3", + "typescript-eslint": "8.71.1" + }, + "engines": { + "node": "24.x" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.153.0.tgz", + "integrity": "sha512-BcWu25dMwMjHpaDCEidmpAi7rGg1qZoihJeUUH9bD55pVZ0z4Wa5CQgqSTo0R41H81oozisQKW3WqvAN2m77IQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.153.0.tgz", + "integrity": "sha512-9l5rdp2DhRqVriBHsNpu5cEf+hizZ4B58rt93HzyQEBUgZiC6qe2oFpaAAnXGCxHGnQExI1s7S2G0QhFacUZ6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.153.0.tgz", + "integrity": "sha512-EcpsyRXHhENDpekpG4QNaSZj2F/BAz4Gh4o89zZwNss86R8KyRSQ6sXbAj74Xyeb4z3j/yBZZ6joQZhraIWyfw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.153.0.tgz", + "integrity": "sha512-hlj80HUQy07/2lYp6rmxH+3u+H2ik3rjLR52CUdPApxmZCNxASDUf/+ykTzAHS/fehftmVMATKvPaWmuLHuasg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.153.0.tgz", + "integrity": "sha512-fWRcv2pMSh1qtez46MOu8vuUJ5fiaQZPHSphbWulbL7SHBMKfRgZl2PzjXZYQJbq7NEXyXbgIuIuUm+/Rmkv8w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.153.0.tgz", + "integrity": "sha512-paZeUK1GXShl/bOJQET1/8AQxayFeuN4SIWAkgTRxE7ZQwv9uYSB2n0648smygP22N7jcI3zxAm0Z+ALNiIKxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.153.0.tgz", + "integrity": "sha512-qFPp5NwMf4K1C0O+DCVGmcbOaYyYAq98Rvv9p4VSfb777iUwlJCqMUxf6QG9N5f5ls4TEX6LitOZHi+6GL6rYQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.153.0.tgz", + "integrity": "sha512-oufEhZCX7saeo27Logra8cvBR1jQ/yw2zUI2hHmWw3qA/XYqEW31FOvLQBv7DWN32NjVdlcaP11WBBZTJo4k9g==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.153.0.tgz", + "integrity": "sha512-zXYzuAqng85h6+LI3iRm0zeMLX1/K2z41ZAMWxVRgt0D10VvZUaV5q+TEB46KDhoKBpdexoTLC6QoB/EiP2oqQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.153.0.tgz", + "integrity": "sha512-J7+NLwLEzdkKM9cKyPHH/AjXXstWOs98KYgLrPPnO4N9MO8EZRBfPNnBcS3S67uMOBkRpUmCl2FhRnWdQ5CbBA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.153.0.tgz", + "integrity": "sha512-sD0K11GQTLWylln54nPMMTtgmBbQrR5It/vMpZpy/fogiw8O2AvIE+9LuWUXddrWk9zvFIHGRcSgFn4IHOfoYg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.153.0.tgz", + "integrity": "sha512-7L66uql2BhvSz51LwfExjOdn6et/ydTiQxl23VApdKzJhTc3TczA1Qt6VR/UcvlQrL2ZbytIJb447vh7p5wmUw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.153.0.tgz", + "integrity": "sha512-hXV3T7mNyrPR19rbPYkglkQJtoM/UNo/xwbZeeqdAxEg8PXgb4LXXuxPpGgI4bzRRCWwLUxdDSS58xIyC2u1TA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.153.0.tgz", + "integrity": "sha512-Da8Vqs1xdtoDYtEjmozD7wGgJeLBQkMbR0WUBECOSxodyX5DXy8ySlvJWbukyO2Q0+TM9EK+4LT6tbq0jCg4hQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.153.0.tgz", + "integrity": "sha512-zGAPxNTIIw6X5WyTRscIFE/oDjpdNtrrl32ing6NOXdGjzRucadnxBilexLdeYQ9YxixF93GujpXyQ0jw7ddSg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.153.0.tgz", + "integrity": "sha512-GoHzpqP5XyPIp9DHokoEdCp+K4UPl/h1neFj1uXnUB3pJYyOQ7NOhFVOttNUgCGcj2eywAvu5cRivTOSq4S10g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.153.0.tgz", + "integrity": "sha512-xGcy12tO9k7zFbeyQzEuilfWfQpyRq23/fLPfN3wjSWuWv58k46fQT1hsO1KFNykZuVGW9xSJE/z592TmAoSDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.153.0.tgz", + "integrity": "sha512-o1ykUjWncvXX6N5a4KebgLjtD8zRui+f1llMhSGoSC9oP2KOqapRpdwmZ3NcNG0axFouwVKmhGBjd+IhA8BksA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.153.0.tgz", + "integrity": "sha512-gxQz7G96Up136fd+2zWrP+o6FYGs9NO4Dyv55IoeJFJUDwLFHcLYY11hhA5wQKs0HoM57Q8esQ3nEVhnCdsjAQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz", + "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz", + "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.24.2.tgz", + "integrity": "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz", + "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz", + "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz", + "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz", + "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz", + "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz", + "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz", + "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz", + "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz", + "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz", + "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz", + "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz", + "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz", + "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz", + "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", + "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz", + "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz", + "integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/type-utils": "8.71.1", + "@typescript-eslint/utils": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz", + "integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz", + "integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.1", + "@typescript-eslint/types": "^8.71.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz", + "integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz", + "integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz", + "integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/utils": "8.71.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz", + "integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz", + "integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.71.1", + "@typescript-eslint/tsconfig-utils": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz", + "integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz", + "integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-jsx-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz", + "integrity": "sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==", + "license": "MIT" + }, + "node_modules/acorn-loose": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.5.2.tgz", + "integrity": "sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz", + "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dependency-cruiser": { + "version": "18.6.0", + "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-18.6.0.tgz", + "integrity": "sha512-It7NW5AmCDBCLMq7I91Nfu8VRKzrIE6S+M/FMQzALDceJ0B1PWRHo77q9YJg/HwyBBEWHVtOuTptIQki+xecOw==", + "license": "MIT", + "dependencies": { + "acorn": "8.19.0", + "acorn-jsx": "5.3.2", + "acorn-jsx-walk": "2.0.0", + "acorn-loose": "8.5.2", + "acorn-walk": "8.3.5", + "commander": "15.0.0", + "enhanced-resolve": "5.26.0", + "ignore": "7.0.12", + "interpret": "3.1.1", + "is-installed-globally": "1.0.1", + "json5": "2.2.3", + "picomatch": "4.0.7", + "prompts": "2.4.2", + "rechoir": "0.8.0", + "safe-regex": "2.1.1", + "semver": "7.8.5", + "tsconfig-paths-webpack-plugin": "4.2.0", + "watskeburt": "6.0.0" + }, + "bin": { + "depcruise": "bin/dependency-cruiser.mjs", + "depcruise-baseline": "bin/depcruise-baseline.mjs", + "depcruise-fmt": "bin/depcruise-fmt.mjs", + "depcruise-wrap-stream-in-html": "bin/wrap-stream-in-html.mjs", + "dependency-cruise": "bin/dependency-cruiser.mjs", + "dependency-cruiser": "bin/dependency-cruiser.mjs" + }, + "engines": { + "node": "^22||^24||>=26" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.452", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz", + "integrity": "sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", + "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-n": { + "version": "18.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.4.1.tgz", + "integrity": "sha512-2sBReWOvCbzgkPri9M5SFmD5EbIwZLa1xz6B5RZsrI19WSFKpyx1sOMr+pdk8puCiXuEgCbErEHy1PuaUrc/4w==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.5.0", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "globrex": "^0.1.2", + "ignore": "^5.3.2", + "js-yaml": "^5.4.1", + "semver": "^7.6.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.57.1", + "ts-declaration-location": "^1.0.6", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "ts-declaration-location": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint-plugin-n/node_modules/js-yaml": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.3.tgz", + "integrity": "sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/eslint-plugin-perfectionist": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.12.1.tgz", + "integrity": "sha512-krdSE7Dz+TCtcVr2+rfNhlOIFFBJcUdoRvjaN7JQnsaVnxJw+BZh9wMkY3MSLCJat+yW+UPTQLKS82Epc7MGZA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.70.0", + "@typescript-eslint/utils": "^8.70.0", + "natural-orderby": "^5.0.0" + }, + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-sonarjs": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.7.tgz", + "integrity": "sha512-62jB20krIPvcwBLAyG3VVKa2ce2j2lL1yCb8Y0ylMRR/dLvCCTiQx8gQbXb+G81k1alPZ2/I3muZinqWQdBbzw==", + "license": "LGPL-3.0-only", + "dependencies": { + "@eslint-community/regexpp": "4.12.2", + "builtin-modules": "3.3.0", + "bytes": "3.1.2", + "functional-red-black-tree": "1.0.1", + "jsx-ast-utils-x": "0.1.0", + "lodash.merge": "4.6.2", + "minimatch": "10.1.2", + "scslre": "0.3.0", + "semver": "7.7.4", + "typescript": ">=5" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "62.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-62.0.0.tgz", + "integrity": "sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "@eslint-community/eslint-utils": "^4.9.0", + "@eslint/plugin-kit": "^0.4.0", + "change-case": "^5.4.4", + "ci-info": "^4.3.1", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.46.0", + "esquery": "^1.6.0", + "find-up-simple": "^1.0.1", + "globals": "^16.4.0", + "indent-string": "^5.0.0", + "is-builtin-module": "^5.0.0", + "jsesc": "^3.1.0", + "pluralize": "^8.0.0", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.13.0", + "semver": "^7.7.3", + "strip-indent": "^4.1.1" + }, + "engines": { + "node": "^20.10.0 || >=21.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=9.38.0" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/formatly": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.11.4.tgz", + "integrity": "sha512-TeCC10lP8KCgoX+Fe5lryILIT0/Dy+nP0OSVnsuB5CwXoLuOdqxCYlPFF1inR8S/zTy1ec7TjhS2TeRKWzzImA==", + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0", + "package-manager-detector": "^1.8.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.14.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT" + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", + "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-5.0.0.tgz", + "integrity": "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==", + "license": "MIT", + "dependencies": { + "builtin-modules": "^5.0.0" + }, + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-builtin-module/node_modules/builtin-modules": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.4.0.tgz", + "integrity": "sha512-JCWSCdun+4Ovd9BhM/Vtlmwb7oD6Wl4YiPRXXwhAuwlPhW1un/MKgXn7GZoFm53ginnoNzus69V8JWx0K393jg==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.1.tgz", + "integrity": "sha512-li88H4QGRoNMX27jEmY8nR515oJVspAgC/WFMlhHfFV8RJ4QyGpHhXnwAe9gH5RIgKmdYRUmrOhQobcgEjIdSw==", + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jscpd": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-5.4.1.tgz", + "integrity": "sha512-afUt+LSQMmzSPYrWZlNGTwj8X4tmwVJbMNmr92qxPf4uUF/naLwG7SQyMPEZtrHqGgzGxmAzNjiwlVdjls2MyQ==", + "license": "MIT", + "bin": { + "jscpd": "run-jscpd.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://opencollective.com/jscpd" + }, + "optionalDependencies": { + "jscpd-darwin-arm64": "5.4.1", + "jscpd-darwin-x64": "5.4.1", + "jscpd-linux-arm64-gnu": "5.4.1", + "jscpd-linux-arm64-musl": "5.4.1", + "jscpd-linux-x64-gnu": "5.4.1", + "jscpd-linux-x64-musl": "5.4.1", + "jscpd-windows-arm64-msvc": "5.4.1", + "jscpd-windows-x64-msvc": "5.4.1" + } + }, + "node_modules/jscpd-darwin-arm64": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-darwin-arm64/-/jscpd-darwin-arm64-5.4.1.tgz", + "integrity": "sha512-LXHVeH60C9BsozcVIMPmXWvj97KqFKbCJYmYG4hDwfwIwS1kz+uF7yiObckP6h2xo12pD88nI4XjWTB5frjZUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/jscpd-darwin-x64": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-darwin-x64/-/jscpd-darwin-x64-5.4.1.tgz", + "integrity": "sha512-Gqck4tcMZjv4IvYHU2Vcikb885zTADo5T/YfEqeu1i5W5tfFpLdViiTYrh5wbrW+uxBqC33gHpG1hy0xbGYknQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/jscpd-linux-arm64-gnu": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-linux-arm64-gnu/-/jscpd-linux-arm64-gnu-5.4.1.tgz", + "integrity": "sha512-XUYZJZVxSY7JIBEoyOOMTgvJwmymkns4wG55tu6I/vRKXnS5QtaTESQfRLt6xj1+62aflAvr01FAhPAy+du6tA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-arm64-musl": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-linux-arm64-musl/-/jscpd-linux-arm64-musl-5.4.1.tgz", + "integrity": "sha512-h2sH8Bk++yBxWqq2QAy4fvPzXkw1vecDSyOmzLwPfnRQoQ2Q/EWxCNOrgJSb+SNMLWoHLLzfqx8m9wt2GG6a8w==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-x64-gnu": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-linux-x64-gnu/-/jscpd-linux-x64-gnu-5.4.1.tgz", + "integrity": "sha512-UiC4YoGAVVLy6hPVz8A6cndo7W5br4FAO2hADnFeinhBPnXPu0QFh1q/s/8B3ldeQHQkpYd/l5cjqNBMrbm78w==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-x64-musl": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-linux-x64-musl/-/jscpd-linux-x64-musl-5.4.1.tgz", + "integrity": "sha512-toAafl5roqfFotPrDJNMY9noNVZ9dReBqcO/nT5pJ8+7aq+opbscaLUcV4+8jxTq5btUD6CiO72+Fu+TyUZv3Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-windows-arm64-msvc": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-windows-arm64-msvc/-/jscpd-windows-arm64-msvc-5.4.1.tgz", + "integrity": "sha512-JLMga/NIwv/OPurPsXA2/d5OdWfdwrsBk2D4NhyKnrV/uNjo1BIi2zXztzDyTQVKMK/TEK6GPgLD143GG1fZ0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/jscpd-windows-x64-msvc": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/jscpd-windows-x64-msvc/-/jscpd-windows-x64-msvc-5.4.1.tgz", + "integrity": "sha512-sN57Y9YP3ijrqc1IsWDceOFHUwbukg9/mU2NSDHTryKKc5CUvWz+o9GId8uKTiJIhkKfR/Ce5GKkCepTwB6kaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jsx-ast-utils-x": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz", + "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/knip": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.41.0.tgz", + "integrity": "sha512-DrhQlIuV6xScd5Q+WTF1znd1w41QzxFGCtAHXCajsH/ndGMotHoNYbdXbuFtzW3mIOmniHtZ7zCtvnQkUmJtQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "formatly": "^0.11.4", + "get-tsconfig": "4.14.3", + "jiti": "^2.7.0", + "oxc-parser": "^0.153.0", + "oxc-resolver": "11.24.2", + "picomatch": "^4.0.7", + "smol-toml": "^1.9.0", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^5.0.0", + "yaml": "^2.9.1", + "zod": "^4.6.5" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nano-spawn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.1.0.tgz", + "integrity": "sha512-yTW+2okrElHiH4fsiz/+/zc0EDo9BDDoC3iKk8dpv1GeRc9nUWzUZHx6TofMWErchhUQR8hY9/Eu1Uja9x1nqA==", + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/natural-orderby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-5.0.0.tgz", + "integrity": "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oxc-parser": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.153.0.tgz", + "integrity": "sha512-cEnpyyV1OJRXv++SFNw0z8i+2o/cr88rVLaOYJafELDoHM5yHvCAbwGRLvxuany6SNLcbblSMUyZaGzH+quSYg==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.153.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.153.0", + "@oxc-parser/binding-android-arm64": "0.153.0", + "@oxc-parser/binding-darwin-arm64": "0.153.0", + "@oxc-parser/binding-darwin-x64": "0.153.0", + "@oxc-parser/binding-freebsd-x64": "0.153.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.153.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.153.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.153.0", + "@oxc-parser/binding-linux-arm64-musl": "0.153.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.153.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.153.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.153.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.153.0", + "@oxc-parser/binding-linux-x64-gnu": "0.153.0", + "@oxc-parser/binding-linux-x64-musl": "0.153.0", + "@oxc-parser/binding-openharmony-arm64": "0.153.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.153.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.153.0", + "@oxc-parser/binding-win32-x64-msvc": "0.153.0" + } + }, + "node_modules/oxc-resolver": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.24.2.tgz", + "integrity": "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.24.2", + "@oxc-resolver/binding-android-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-x64": "11.24.2", + "@oxc-resolver/binding-freebsd-x64": "11.24.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-musl": "11.24.2", + "@oxc-resolver/binding-openharmony-arm64": "11.24.2", + "@oxc-resolver/binding-wasm32-wasi": "11.24.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", + "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", + "license": "MIT" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", + "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rechoir/node_modules/resolve": { + "version": "1.22.13", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz", + "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.17.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regjsparser": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.4.tgz", + "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smol-toml": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.1.tgz", + "integrity": "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.71.1", + "@typescript-eslint/parser": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/utils": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbash": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-5.0.0.tgz", + "integrity": "sha512-RYNm/ISFtflWpIcXTPHHLJZ0mqzInav+L9ke9PcL+hHsSf9+Fd/syPb614Mlqs/O3AYgjnvxDOFl0a+zVqi1/w==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/watskeburt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-6.0.0.tgz", + "integrity": "sha512-jfiuDABaxSkC71T6oZ3vCS99roYkSHm/+As+G0Dz8taAHQb+SJBvLEm5RlsgG71XdfAj3rv7eudUBTgwcQUPlQ==", + "license": "MIT", + "bin": { + "watskeburt": "dist/run-cli.js" + }, + "engines": { + "node": "^22.13||^24||>=26" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.6.tgz", + "integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/tools/quality/package.json b/tools/quality/package.json new file mode 100644 index 0000000..62d20c2 --- /dev/null +++ b/tools/quality/package.json @@ -0,0 +1,34 @@ +{ + "name": "@1msg/quality-template", + "version": "1.0.2", + "private": true, + "type": "module", + "engines": { + "node": "24.x" + }, + "scripts": { + "test": "node --test test/*.test.mjs" + }, + "dependencies": { + "@eslint/js": "9.39.5", + "dependency-cruiser": "18.6.0", + "eslint": "9.39.5", + "eslint-import-resolver-typescript": "4.4.5", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-n": "18.4.1", + "eslint-plugin-perfectionist": "5.12.1", + "eslint-plugin-react": "7.37.5", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-sonarjs": "3.0.7", + "eslint-plugin-unicorn": "62.0.0", + "globals": "17.13.0", + "husky": "9.1.7", + "jscpd": "5.4.1", + "knip": "6.41.0", + "lint-staged": "16.2.7", + "picomatch": "4.0.7", + "prettier": "3.9.10", + "typescript": "5.9.3", + "typescript-eslint": "8.71.1" + } +} diff --git a/tools/quality/prettier.config.mjs b/tools/quality/prettier.config.mjs new file mode 100644 index 0000000..9d0c3e3 --- /dev/null +++ b/tools/quality/prettier.config.mjs @@ -0,0 +1,2 @@ +/** One formatting policy shared by authored code and the quality template itself. */ +export default { singleQuote: true, trailingComma: 'all' }; diff --git a/tools/quality/run.mjs b/tools/quality/run.mjs new file mode 100644 index 0000000..e0b21ae --- /dev/null +++ b/tools/quality/run.mjs @@ -0,0 +1,360 @@ +#!/usr/bin/env node +import { + readFileSync, + writeFileSync, + mkdirSync, + mkdtempSync, + rmSync, +} from 'node:fs'; +import { dirname, resolve, join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { + execute, + baseRef, + baseFile, + currentFile, + git, + changedFiles, +} from './lib/git.mjs'; +import { assess, parseJson } from './lib/baseline.mjs'; +import { runGuards, validateConfig } from './lib/guards.mjs'; +import { + knipRecords, + duplicateRecords, + eslintRecords, + architectureRecords, + tokenFingerprint, +} from './lib/reports.mjs'; +import { historicalAnalysis } from './lib/historical.mjs'; + +const directory = dirname(fileURLToPath(import.meta.url)); +const args = process.argv.slice(2); +const action = args[0]; + +/** Read an option without treating subsequent flags as values. */ +function option(name, fallback) { + const position = args.indexOf(name); + if (position < 0) return fallback; + const value = args[position + 1]; + if (!value || value.startsWith('--')) + throw new Error(`Missing value for ${name}`); + return value; +} + +/** Record analyzer exit state together with the report; non-diagnostic exits always fail. */ +function analyze(command, argv, name) { + const result = execute(command, argv); + if (![0, 1].includes(result.status)) + throw new Error(`${name} failed (${result.status}): ${result.stderr}`); + if (result.stderr.trim()) process.stderr.write(result.stderr); + return { status: result.status, report: parseJson(result.stdout, name) }; +} + +/** Run Knip's actual production or full graph with its independent config and JSON reporter. */ +function unused(config, production) { + const argv = [ + join(directory, 'node_modules/knip/bin/knip.js'), + '--config', + config.knipConfig ?? 'knip.json', + '--reporter', + join(directory, 'lib/knip-reporter.mjs'), + ]; + if (production) argv.push('--production'); + const { report } = analyze(process.execPath, argv, 'Knip'); + return { findings: knipRecords(report), report }; +} + +/** Run duplicate analysis into a fresh temporary directory, rejecting empty or missing output. */ +function duplicates(config) { + if (!config.duplicatePaths?.length) + throw new Error('duplicatePaths must list authored runtime/tooling roots'); + const output = mkdtempSync(join(tmpdir(), '1msg-jscpd-')); + try { + const argv = [ + join(directory, 'node_modules/jscpd/run-jscpd.js'), + ...config.duplicatePaths, + '--reporters', + 'json', + '--output', + output, + '--mode', + 'mild', + '--min-lines', + '5', + '--min-tokens', + '50', + '--silent', + '--absolute', + '--format', + ( + config.duplicateFormats ?? [ + 'javascript', + 'typescript', + 'jsx', + 'tsx', + 'svelte', + ] + ).join(','), + ]; + if (config.duplicateIgnore?.length) + argv.push('--ignore', config.duplicateIgnore.join(',')); + const result = execute(process.execPath, argv); + if (result.status !== 0) + throw new Error( + `jscpd failed (${result.status}): ${result.stderr || result.stdout}`, + ); + const report = parseJson( + readFileSync(join(output, 'jscpd-report.json'), 'utf8'), + 'jscpd', + ); + return { findings: duplicateRecords(report), report }; + } finally { + rmSync(output, { recursive: true, force: true }); + } +} + +/** Confirm initial allowances describe existing source; freshly introduced problems cannot be bootstrapped. */ +function initialEvidence(finding, base, historical) { + const prior = historical.find((item) => item.key === finding.key); + if (!prior || finding.value > prior.value) + throw new Error(`Cannot baseline a new/increased finding: ${finding.key}`); + const evidence = {}; + for (const path of finding.paths ?? [finding.path]) { + if (!path) throw new Error(`Finding lacks source evidence: ${finding.key}`); + const before = baseFile(base, path); + const current = currentFile(path); + if (before === null || current === null) + throw new Error(`Cannot baseline a new/missing file: ${path}`); + if (path.endsWith('package.json') && finding.symbol) { + const old = JSON.parse(before); + const dependencies = { + ...old.dependencies, + ...old.devDependencies, + ...old.optionalDependencies, + }; + evidence[path] = + finding.symbol in dependencies + ? `dependency:${finding.symbol}:${dependencies[finding.symbol]}` + : tokenFingerprint(before); + } else { + evidence[path] = tokenFingerprint(before); + } + } + return evidence; +} + +/** Write explicit historical allowances locally, then validate the same artifact as CI. */ +function bootstrap(findings, path, base, historical) { + if (process.env.CI) throw new Error('Baseline bootstrap is forbidden in CI'); + const owner = option('--owner'); + const expires = option('--expires'); + const reason = option('--reason'); + const issue = option('--issue'); + if (!owner || !expires || !reason || !issue) + throw new Error( + 'Bootstrap requires --owner, --expires, --reason and --issue', + ); + const entries = findings.map((finding) => ({ + ...finding, + owner, + expires, + reason, + issue, + evidence: initialEvidence(finding, base, historical), + })); + const baseline = { schemaVersion: 1, adoptionBase: base, entries }; + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, `${JSON.stringify(baseline, null, 2)}\n`); + return baseline; +} + +/** Ratchet individual debt against trusted Git history, including reductions and expired allowances. */ +function gate(config, name, result, base, analyzeBase) { + if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error('Invalid gate name'); + const path = `${config.baselineDir ?? 'quality/baselines'}/${name}.json`; + const raw = currentFile(path); + const old = baseFile(base, path); + const previous = old === null ? null : parseJson(old, 'trusted baseline'); + const historical = + previous || result.findings.length === 0 + ? [] + : historicalAnalysis(base, config, directory, analyzeBase).findings; + const baseline = args.includes('--bootstrap') + ? bootstrap(result.findings, path, base, historical) + : raw === null + ? { schemaVersion: 1, entries: [] } + : parseJson(raw, 'baseline'); + if (!previous && baseline.entries.length) { + if (baseline.adoptionBase !== base) + throw new Error('Initial baseline is not tied to the trusted base SHA'); + for (const entry of baseline.entries) { + if ( + JSON.stringify(entry.evidence) !== + JSON.stringify(initialEvidence(entry, base, historical)) + ) { + throw new Error(`Initial baseline evidence changed: ${entry.key}`); + } + } + } + const errors = assess(result.findings, baseline, previous); + if (name === 'duplicates') { + if ( + !Number.isFinite(config.duplicateMaxPercentage) || + config.duplicateMaxPercentage < 0 || + config.duplicateMaxPercentage > 100 + ) { + throw new Error( + 'duplicateMaxPercentage must specify the inventoried numeric ceiling (0–100)', + ); + } + const percentage = result.report.statistics.total.percentage; + if (!Number.isFinite(percentage)) + throw new Error('jscpd omitted its numeric duplicate percentage'); + if (percentage > config.duplicateMaxPercentage) + errors.push( + `Duplicate percentage ${percentage} exceeds ${config.duplicateMaxPercentage}`, + ); + } + mkdirSync('quality/reports', { recursive: true }); + writeFileSync( + `quality/reports/${name}.json`, + `${JSON.stringify( + { + schemaVersion: 1, + head: git(['rev-parse', 'HEAD']).trim(), + base, + result: errors.length ? 'FAIL' : 'PASS', + findings: result.findings, + errors, + analyzer: result.report, + }, + null, + 2, + )}\n`, + ); + if (errors.length) throw new Error(errors.join('\n')); + console.log( + `${name}: PASS (${result.findings.length} existing, individually tracked violations)`, + ); +} + +/** Dispatch one mandatory check; unsupported actions or missing configuration never count as success. */ +async function main() { + const config = ( + await import(pathToFileURL(resolve('quality.config.mjs')).href) + ).default; + validateConfig(config); + const base = baseRef( + config.defaultBranch, + option('--base', process.env.QUALITY_BASE), + ); + if (action === 'changed-files') { + console.log(JSON.stringify(changedFiles(base))); + return; + } + if (action === 'guards') { + const result = runGuards(config, base); + if (result.errors.length) throw new Error(result.errors.join('\n')); + console.log( + `guards: PASS (${result.checked} authored source files inventoried)`, + ); + return; + } + if (['unused-production', 'unused-full'].includes(action)) { + const analyzeBase = (sourceConfig) => + unused(sourceConfig, action === 'unused-production'); + gate(config, action, analyzeBase(config), base, analyzeBase); + return; + } + if (action === 'duplicates') { + gate(config, action, duplicates(config), base, duplicates); + return; + } + if (action === 'duplicates-full') { + if (!config.duplicateFullPaths?.length) + throw new Error( + 'duplicateFullPaths must inventory runtime, tests and tooling', + ); + const result = duplicates({ + ...config, + duplicatePaths: config.duplicateFullPaths, + duplicateIgnore: + config.duplicateFullIgnore ?? + (config.generated ?? []).map((entry) => entry.pattern), + }); + mkdirSync('quality/reports', { recursive: true }); + writeFileSync( + 'quality/reports/duplicates-full.json', + `${JSON.stringify( + { + schemaVersion: 1, + head: git(['rev-parse', 'HEAD']).trim(), + base, + result: 'REPORT', + ...result, + }, + null, + 2, + )}\n`, + ); + console.log( + `duplicates-full: analyzed ${result.report.statistics.total.sources} files; report saved`, + ); + return; + } + if (action === 'report' && option('--kind') === 'eslint') { + const split = args.indexOf('--'); + if (split < 0 || !args[split + 1]) + throw new Error('report requires -- '); + const analyzeBase = () => { + const { report } = analyze( + args[split + 1], + args.slice(split + 2), + 'ESLint', + ); + return { findings: eslintRecords(report), report }; + }; + gate( + config, + option('--name', 'complexity'), + analyzeBase(), + base, + analyzeBase, + ); + return; + } + if (action === 'report' && option('--kind') === 'depcruiser') { + const split = args.indexOf('--'); + if (split < 0 || !args[split + 1]) + throw new Error('report requires -- '); + const analyzeBase = () => { + const result = execute(args[split + 1], args.slice(split + 2)); + const report = parseJson(result.stdout, 'dependency-cruiser'); + if ( + !Number.isInteger(report.summary?.advisedExitCode) || + (result.status !== 0 && + result.status !== report.summary.advisedExitCode % 256) + ) { + throw new Error( + `dependency-cruiser exit/report mismatch (${result.status}): ${result.stderr}`, + ); + } + return { findings: architectureRecords(report), report }; + }; + gate( + config, + option('--name', 'architecture'), + analyzeBase(), + base, + analyzeBase, + ); + return; + } + throw new Error(`Unknown quality action: ${action}`); +} + +main().catch((error) => { + console.error(error.message); + process.exitCode = 1; +}); diff --git a/tools/quality/seal.mjs b/tools/quality/seal.mjs new file mode 100644 index 0000000..8e2c5b9 --- /dev/null +++ b/tools/quality/seal.mjs @@ -0,0 +1,19 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { digest } from './lib/baseline.mjs'; +import { templateFiles } from './lib/template.mjs'; + +// Maintainers seal a reviewed template version before copying it into consumer repositories. +const root = dirname(fileURLToPath(import.meta.url)); +const { version } = JSON.parse(readFileSync(`${root}/package.json`, 'utf8')); +const files = Object.fromEntries( + templateFiles(root).map((path) => [ + path, + digest(readFileSync(`${root}/${path}`, 'utf8')), + ]), +); +writeFileSync( + `${root}/template-manifest.json`, + `${JSON.stringify({ schemaVersion: 1, version, files }, null, 2)}\n`, +); diff --git a/tools/quality/template-manifest.json b/tools/quality/template-manifest.json new file mode 100644 index 0000000..444e17d --- /dev/null +++ b/tools/quality/template-manifest.json @@ -0,0 +1,25 @@ +{ + "schemaVersion": 1, + "version": "1.0.2", + "files": { + "README.md": "1d3f6f9d420adea402d92aff483c14271e6e24c291437d6ea5b59c3162702a20", + "eslint.mjs": "607ddfaac3d365fedd7dbc5e21c781d448347b1c36662f3492e4364ff9f22ded", + "lib/baseline.mjs": "7876ba90b80448d9c9003c21e348095ed22630c41fc26ed944984742020549dd", + "lib/git.mjs": "b7c26871526d864bf8d12f01c613d3bfa0adb2da9d072544d8f09407ef598758", + "lib/guards.mjs": "310c224c5d072fa16b1cf5f0c47e5da4258f0f63fa9a1c4dc114c9fe192bc398", + "lib/historical.mjs": "d338d88bae6d1da19f27296fbb6c57cff4ca09664fe7baa8d6c8d801ca3e69f2", + "lib/knip-reporter.mjs": "175d7d96e2913e8f6de04b6c706172258d9d7654a26ba7f9ed7edff79d3fc8ca", + "lib/reports.mjs": "f47f6cbf8022691e61a245bf54a5fdd1ba9f2037906ec6576f2ac771224161dc", + "lib/template.mjs": "c2a14b715f0c9dbe8b8243b142f4c6628967400fb8f92767537169e1b7bfd8f4", + "package-lock.json": "931d28ffff6590892a072569df9c57345d0a388a22598b3482dee53136a52828", + "package.json": "b2c70073c423135e4b5abba9507d58fd5136a676339e816b72d6dd4bcbb1b757", + "prettier.config.mjs": "7576563a9a30aff8eb3177f9cc2e4bc7a0f6b825042eadb9a619b86f519c5e5e", + "run.mjs": "b60c0fc2419c8cfd32eddf369da38b2d87e2b7eacbc31fda31c598930900f199", + "seal.mjs": "fb38c8e08ea3cf9f4dcd95e45ffc931aea3b7a54181fed88d7379f8aad4c050d", + "test/cli.test.mjs": "f743a18f0763cf5b5f42f8d2a4b71f67a4de900a3955a299309395af95b99cf3", + "test/ratchet.test.mjs": "62fef1cc7a1851db81c30966909968ff84637b8ed8003821120269c4fae70d49", + "tsconfig.next.json": "d900a35fcb0852376b06854df5be2f8f178e26cb5383b93b07ebeb6220e30faf", + "tsconfig.node.json": "627661abcb7e992c4bfaf98d6e68a12678573b5d4f6e821ea0fa43fa6e233f28", + "tsconfig.svelte.json": "5777247ea98aaf2d886b87cd12206a641377edcdd086ce5fd5b58adc6f0bc771" + } +} diff --git a/tools/quality/test/cli.test.mjs b/tools/quality/test/cli.test.mjs new file mode 100644 index 0000000..897d9cd --- /dev/null +++ b/tools/quality/test/cli.test.mjs @@ -0,0 +1,510 @@ +import { templateFiles } from '../lib/template.mjs'; +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + mkdtempSync, + mkdirSync, + writeFileSync, + rmSync, + cpSync, + readFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawnSync } from 'node:child_process'; + +const runner = fileURLToPath(new URL('../run.mjs', import.meta.url)); + +/** Create a real Git fixture so CLI tests exercise trusted base and working-tree changes together. */ +function fixture() { + const cwd = mkdtempSync(join(tmpdir(), '1msg-quality-test-')); + mkdirSync(join(cwd, 'src')); + writeFileSync( + join(cwd, 'package.json'), + '{"name":"quality-fixture","private":true,"type":"module"}\n', + ); + writeFileSync( + join(cwd, 'quality.config.mjs'), + `export default { + schemaVersion:1,profile:'A',defaultBranch:'main',sourceRoots:['src','quality.config.mjs'], + knipConfig:'knip.json',duplicatePaths:['src'],duplicateMaxPercentage:100 + };\n`, + ); + writeFileSync( + join(cwd, 'knip.json'), + JSON.stringify({ entry: ['src/index.js!'], project: ['src/**/*.js!'] }), + ); + const git = (...args) => { + const result = spawnSync('git', args, { cwd, encoding: 'utf8' }); + assert.equal(result.status, 0, result.stderr); + return result.stdout.trim(); + }; + git('init', '-b', 'main'); + git('config', 'user.email', 'quality@example.invalid'); + git('config', 'user.name', 'Quality fixture'); + const commit = () => { + git('add', '.'); + git('commit', '-m', 'test: fixture'); + return git('rev-parse', 'HEAD'); + }; + const run = (...args) => + spawnSync(process.execPath, [runner, ...args], { + cwd, + encoding: 'utf8', + env: { ...process.env, CI: '', QUALITY_BASE: undefined }, + timeout: 60_000, + maxBuffer: 16 * 1024 * 1024, + }); + return { + cwd, + git, + commit, + run, + write: (path, text) => writeFileSync(join(cwd, path), text), + close: () => rmSync(cwd, { recursive: true, force: true }), + }; +} + +test('real CLI blocks undocumented new functions, broad suppression and uncovered files', () => { + const f = fixture(); + try { + f.write('src/index.js', 'console.log("app");\n'); + const base = f.commit(); + assert.equal(f.run('guards', '--base', base).status, 0); + f.write( + 'src/index.js', + 'function createWidget() { return 2; }\nconsole.log(createWidget());\n', + ); + assert.match( + f.run('guards', '--base', base).stderr, + /document createWidget/, + ); + f.write( + 'src/index.js', + '/** Construct the default widget. */\nfunction createWidget() { return 2; }\nconsole.log(createWidget());\n', + ); + assert.equal(f.run('guards', '--base', base).status, 0); + f.write('src/index.js', '// @ts-nocheck\nconsole.log("app");\n'); + assert.match(f.run('guards', '--base', base).stderr, /broad lint/); + f.write('rogue.js', 'console.log("forgotten");\n'); + assert.match( + f.run('guards', '--base', base).stderr, + /no declared source root/, + ); + assert.notEqual(f.run('guards', '--base', 'missing-ref').status, 0); + } finally { + f.close(); + } +}); + +test('actual Knip CLI blocks an unused file and unresolved import', () => { + const f = fixture(); + try { + f.write('src/index.js', 'console.log("app");\n'); + const base = f.commit(); + assert.equal(f.run('unused-full', '--base', base).status, 0); + f.write('src/forgotten.js', 'export const value = 42;\n'); + const unused = f.run('unused-full', '--base', base); + assert.equal(unused.status, 1, unused.stderr); + assert.match(unused.stderr, /forgotten/); + f.write( + 'src/index.js', + 'import value from "./missing.js"; console.log(value);\n', + ); + assert.match( + f.run('unused-production', '--base', base).stderr, + /unresolved/, + ); + } finally { + f.close(); + } +}); + +test('documentation survives formatting and unrelated callback removal, but new copies still require docs', () => { + const f = fixture(); + try { + f.write( + 'src/index.js', + ` + run(() => { return {name: "removed"}; }); + run(() => { return {name: "retained"}; }); + function load(value) { return request(value, {name: "widget"}); } + `, + ); + const base = f.commit(); + f.write( + 'src/index.js', + ` + run(() => { return {name: 'retained',}; }); + function load(value,) { return request(value, {name: 'widget',},); } + `, + ); + const formatted = f.run('guards', '--base', base); + assert.equal(formatted.status, 0, formatted.stderr); + f.write( + 'src/index.js', + ` + run(() => { return {name: 'retained',}; }); + run(() => { return {name: 'retained',}; }); + function load(value,) { return request(value, {name: 'widget',},); } + `, + ); + const duplicated = f.run('guards', '--base', base); + assert.equal(duplicated.status, 1, duplicated.stderr); + assert.match(duplicated.stderr, /document /); + } finally { + f.close(); + } +}); + +test('actual jscpd CLI blocks a new clone even with unrelated unique lines', () => { + const f = fixture(); + try { + const source = `export function summarize(values) { + const result = []; + for (const value of values) { + const normalized = String(value).trim(); + if (normalized.length > 0) { + result.push({ value: normalized, length: normalized.length }); + } + } + return result.sort((left, right) => left.length - right.length); + }\n`; + f.write('src/index.js', source); + const base = f.commit(); + const clean = f.run('duplicates', '--base', base); + assert.equal(clean.status, 0, clean.stderr); + f.write( + 'src/copy.js', + source + '\nexport const unrelated = "extra unique content";\n', + ); + const duplicated = f.run('duplicates', '--base', base); + assert.equal(duplicated.status, 1, duplicated.stderr); + assert.match(duplicated.stderr, /New or increased/); + } finally { + f.close(); + } +}); + +test('Knip production rejects runtime code referenced only from tests while full accepts it', () => { + const f = fixture(); + try { + f.write( + 'knip.json', + JSON.stringify({ + entry: ['src/index.js!', 'test.js'], + project: ['src/**/*.js!', 'test.js'], + }), + ); + f.write('src/index.js', 'console.log("app");\n'); + f.write('src/helper.js', 'export const value = 42;\n'); + f.write( + 'test.js', + 'import { value } from "./src/helper.js"; console.log(value);\n', + ); + const base = f.commit(); + const full = f.run('unused-full', '--base', base); + assert.equal(full.status, 0, full.stderr); + const production = f.run('unused-production', '--base', base); + assert.equal(production.status, 1, production.stderr); + assert.match(production.stderr, /helper/); + } finally { + f.close(); + } +}); + +test('initial debt is replayed against trusted source, including edited files and newly disconnected imports', () => { + const f = fixture(); + const bootstrap = [ + '--bootstrap', + '--owner', + 'quality', + '--expires', + '2099-01-01', + '--reason', + 'historical fixture debt', + '--issue', + 'TEST-1', + ]; + try { + f.write( + 'src/index.js', + 'import { value } from "./helper.js"; console.log(value);\n', + ); + f.write('src/helper.js', 'export const value = 42;\n'); + f.write('src/forgotten.js', 'export const oldDebt = 1;\n'); + const base = f.commit(); + f.write('src/forgotten.js', 'export const oldDebt = 2;\n'); + f.write('new-tool.mjs', 'console.log(123);'); + f.write( + 'knip.json', + JSON.stringify({ + entry: ['src/index.js!', 'new-tool.mjs'], + project: ['src/**/*.js!', 'new-tool.mjs'], + }), + ); + const historical = f.run('unused-full', '--base', base, ...bootstrap); + assert.equal(historical.status, 0, historical.stderr); + f.write('src/index.js', 'console.log("now disconnected");\n'); + const newDebt = f.run('unused-full', '--base', base, ...bootstrap); + assert.equal(newDebt.status, 1, newDebt.stderr); + assert.match( + newDebt.stderr, + /Cannot baseline a new\/increased finding.*helper/, + ); + } finally { + f.close(); + } +}); + +test('jscpd preserves multiple source roots and enforces its numeric ceiling', () => { + const f = fixture(); + try { + mkdirSync(join(f.cwd, 'runtime')); + const source = `export function summarize(values) { + const result = []; + for (const value of values) { + const normalized = String(value).trim(); + if (normalized.length > 0) result.push({ value: normalized, length: normalized.length }); + } + return result.sort((left, right) => left.length - right.length); + }\n`; + f.write('src/index.js', source); + f.write('runtime/copy.js', source); + f.write( + 'quality.config.mjs', + `export default { schemaVersion:1,profile:'A',sourceRoots:['src','runtime'], + duplicatePaths:['src','runtime'],duplicateMaxPercentage:100 };`, + ); + const base = f.commit(); + const bootstrap = [ + '--bootstrap', + '--owner', + 'quality', + '--expires', + '2099-01-01', + '--reason', + 'historical fixture debt', + '--issue', + 'TEST-1', + ]; + const accepted = f.run('duplicates', '--base', base, ...bootstrap); + assert.equal(accepted.status, 0, accepted.stderr); + f.write( + 'quality.config.mjs', + `export default { schemaVersion:1,profile:'A',sourceRoots:['src','runtime'], + duplicatePaths:['src','runtime'],duplicateMaxPercentage:0 };`, + ); + const percentage = f.run('duplicates', '--base', base); + assert.equal(percentage.status, 1, percentage.stderr); + assert.match(percentage.stderr, /Duplicate percentage/); + } finally { + f.close(); + } +}); + +test('actual dependency-cruiser detects a long runtime cycle and refuses native I/O debt', () => { + const f = fixture(); + const executable = fileURLToPath( + new URL( + '../node_modules/dependency-cruiser/bin/dependency-cruiser.mjs', + import.meta.url, + ), + ); + const command = [ + '--', + process.execPath, + executable, + 'src', + '--config', + 'dependency-cruiser.config.cjs', + '--output-type', + 'json', + ]; + try { + f.write( + 'dependency-cruiser.config.cjs', + `module.exports = { forbidden: [ + {name:'no-runtime-cycles',severity:'error',from:{},to:{circular:true,dependencyTypesNot:['type-only']}}, + {name:'application-no-native-io',severity:'error',from:{path:'src'},to:{dependencyTypes:['core']}}, + ] };`, + ); + f.write('src/index.js', 'import "./second.js"; console.log("one");\n'); + f.write('src/second.js', 'import "./third.js"; console.log("two");\n'); + f.write('src/third.js', 'console.log("three");\n'); + const base = f.commit(); + const clean = f.run( + 'report', + '--kind', + 'depcruiser', + '--base', + base, + ...command, + ); + assert.equal(clean.status, 0, clean.stderr); + f.write('src/third.js', 'import "./index.js"; console.log("three");\n'); + const cycle = f.run( + 'report', + '--kind', + 'depcruiser', + '--base', + base, + ...command, + ); + assert.equal(cycle.status, 1, cycle.stderr); + assert.match(cycle.stderr, /no-runtime-cycles/); + f.write('src/third.js', 'import fs from "node:fs"; console.log(fs);\n'); + const native = f.run( + 'report', + '--kind', + 'depcruiser', + '--base', + base, + '--bootstrap', + '--owner', + 'quality', + '--expires', + '2099-01-01', + '--reason', + 'cannot bless native dependency', + '--issue', + 'TEST-1', + ...command, + ); + assert.equal(native.status, 1, native.stderr); + assert.match(native.stderr, /Non-baselinable architecture/); + } finally { + f.close(); + } +}); + +test('vendored exemption rejects unregistered and modified template files', () => { + const f = fixture(); + try { + f.write('src/index.js', 'console.log("app");\n'); + const base = f.commit(); + const template = fileURLToPath(new URL('..', import.meta.url)); + for (const path of [...templateFiles(template), 'template-manifest.json']) { + const destination = join(f.cwd, 'tools/quality', path); + mkdirSync(dirname(destination), { recursive: true }); + cpSync(join(template, path), destination); + } + const clean = f.run('guards', '--base', base); + assert.equal(clean.status, 0, clean.stderr); + f.write('tools/quality/hidden.js', 'export const hidden = 1;'); + assert.match(f.run('guards', '--base', base).stderr, /undeclared/); + rmSync(join(f.cwd, 'tools/quality/hidden.js')); + f.write('tools/quality/README.md', 'unreviewed mutation'); + assert.match(f.run('guards', '--base', base).stderr, /integrity mismatch/); + } finally { + f.close(); + } +}); + +/** Real embedded Svelte clones keep language identity but historical evidence belongs to their physical component. */ +test('jscpd Svelte fragments bootstrap existing clones and reject another participant', () => { + const f = fixture(); + try { + const component = ` +

Summary

\n`; + f.write('src/First.svelte', component); + f.write('src/Second.svelte', component); + const base = f.commit(); + const accepted = f.run( + 'duplicates', + '--base', + base, + '--bootstrap', + '--owner', + 'quality', + '--expires', + '2099-01-01', + '--reason', + 'historical Svelte fixture debt', + '--issue', + 'TEST-1', + ); + assert.equal(accepted.status, 0, accepted.stderr); + const report = JSON.parse( + readFileSync(join(f.cwd, 'quality/reports/duplicates.json'), 'utf8'), + ); + assert.ok( + report.findings.some((finding) => + finding.participants?.some((path) => + path.endsWith('.svelte:javascript'), + ), + ), + ); + assert.ok( + report.findings.every((finding) => + finding.paths.every((path) => path.endsWith('.svelte')), + ), + ); + f.write('src/Third.svelte', component); + const expanded = f.run('duplicates', '--base', base); + assert.equal(expanded.status, 1, expanded.stderr); + assert.match(expanded.stderr, /New or increased/); + } finally { + f.close(); + } +}); + +/** Only the new sealed vendor exemption is omitted from historical graphs; authored stale ignores still fail. */ +test('historical Knip removes a current-only exact vendor ignore without relaxing authored coverage', () => { + const f = fixture(); + try { + f.write('src/index.js', 'console.log("app");\n'); + f.write('src/forgotten.js', 'export const historicalDebt = 1;\n'); + const base = f.commit(); + const template = fileURLToPath(new URL('..', import.meta.url)); + for (const path of [...templateFiles(template), 'template-manifest.json']) { + const destination = join(f.cwd, 'tools/quality', path); + mkdirSync(dirname(destination), { recursive: true }); + cpSync(join(template, path), destination); + } + f.write( + 'quality.mjs', + 'import { record } from "./tools/quality/lib/baseline.mjs"; console.log(record("example"));\n', + ); + const config = { + entry: ['src/index.js!', 'quality.mjs'], + project: ['src/**/*.js!', '**/*.mjs'], + ignore: ['tools/quality/**'], + }; + f.write('knip.json', JSON.stringify(config)); + const accepted = f.run( + 'unused-full', + '--base', + base, + '--bootstrap', + '--owner', + 'quality', + '--expires', + '2099-01-01', + '--reason', + 'historical source debt', + '--issue', + 'TEST-1', + ); + assert.equal(accepted.status, 0, accepted.stderr); + config.ignore.push('src/missing/**'); + f.write('knip.json', JSON.stringify(config)); + const invalid = f.run('unused-full', '--base', base); + assert.equal(invalid.status, 1, invalid.stderr); + assert.match(invalid.stderr, /stale exclusion failure.*src\/missing/); + } finally { + f.close(); + } +}); diff --git a/tools/quality/test/ratchet.test.mjs b/tools/quality/test/ratchet.test.mjs new file mode 100644 index 0000000..7722924 --- /dev/null +++ b/tools/quality/test/ratchet.test.mjs @@ -0,0 +1,238 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { assess, parseJson } from '../lib/baseline.mjs'; +import { + knipRecords, + duplicateRecords, + eslintRecords, + tokenFingerprint, +} from '../lib/reports.mjs'; +import { functionsIn, suppressionErrors } from '../lib/guards.mjs'; + +const owner = { + owner: 'quality-maintainers', + reason: 'Existing complexity', + issue: '1MSG-156', + expires: '2026-11-12', +}; +const baseline = (entries) => ({ schemaVersion: 1, entries }); +const allowance = (key, value) => ({ key, value, ...owner }); + +test('growth cannot be offset by reductions, and reductions retire allowances', () => { + const old = baseline([allowance('a', 25), allowance('b', 20)]); + assert.match( + assess( + [ + { key: 'a', value: 26 }, + { key: 'b', value: 15 }, + ], + old, + old, + '2026-10-12', + ).join('\n'), + /increased/, + ); + assert.match( + assess( + [ + { key: 'a', value: 25 }, + { key: 'b', value: 15 }, + ], + old, + old, + '2026-10-12', + ).join('\n'), + /Reduce/, + ); +}); + +test('new or increased baselines, expiry extensions and stale allowances fail', () => { + const old = baseline([allowance('a', 25)]); + const next = baseline([ + { ...allowance('a', 26), expires: '2026-12-12' }, + allowance('new', 1), + ]); + const errors = assess( + [{ key: 'a', value: 26 }], + next, + old, + '2026-10-12', + ).join('\n'); + assert.match(errors, /expanded/); + assert.match(errors, /expiry extended/); + assert.match(errors, /stale/); + assert.match(assess([], old, old, '2026-12-01').join('\n'), /Expired/); +}); + +test('empty or malformed analyzer reports are not successful checks', () => { + assert.throws(() => parseJson('Killed', 'Knip'), /complete JSON/); + assert.throws(() => knipRecords({}), /schema/); + assert.throws( + () => + duplicateRecords({ + duplicates: [], + statistics: { total: { sources: 0 } }, + }), + /empty/, + ); + assert.throws(() => eslintRecords([]), /empty/); +}); + +test('Knip distinguishes unresolved imports, unused dependencies and exported symbols', () => { + const rows = knipRecords({ + issues: [ + { + file: 'src/a.ts', + exports: [{ name: 'thing', line: 3 }], + unresolved: [{ name: './missing', line: 1 }], + files: [], + }, + { file: 'package.json', dependencies: [{ name: 'unused' }] }, + ], + }); + assert.equal(rows.length, 3); + assert.ok(rows.some((row) => row.rule === 'unresolved')); + const moved = knipRecords({ + issues: [{ file: 'src/a.ts', exports: [{ name: 'thing', line: 99 }] }], + }); + assert.equal(rows.find((row) => row.rule === 'exports').key, moved[0].key); +}); + +test('duplicate identity ignores line moves but notices additional participants and extended code', () => { + const clone = { + format: 'typescript', + fragment: 'const value = input + 3;', + tokens: 7, + firstFile: { name: 'src/a.ts', start: 1, end: 2 }, + secondFile: { name: 'src/b.ts', start: 7, end: 8 }, + }; + const report = (clones) => ({ + duplicates: clones, + statistics: { total: { sources: 3 } }, + }); + const original = duplicateRecords(report([clone])); + const moved = duplicateRecords( + report([ + { ...clone, firstFile: { ...clone.firstFile, start: 9, end: 10 } }, + ]), + ); + assert.equal(original[0].key, moved[0].key); + assert.notEqual( + original[0].key, + duplicateRecords( + report([clone, { ...clone, secondFile: { name: 'src/c.ts' } }]), + )[0].key, + ); + assert.notEqual( + original[0].key, + duplicateRecords( + report([{ ...clone, fragment: clone.fragment + 'return value;' }]), + )[0].key, + ); + assert.equal( + tokenFingerprint('const a = 1;'), + tokenFingerprint('/* moved */ const a=1;'), + ); + assert.notEqual( + tokenFingerprint('const a="a b";'), + tokenFingerprint('const a="ab";'), + ); +}); + +test('documentation covers private methods, arrow helpers and Svelte scripts', () => { + const source = + '/** Resolve the current owner. */\nconst owner = () => 1;\nclass Foo {\n/** Release the acquired resource. */\nprivate release() {}\n}'; + const functions = functionsIn('src/a.ts', source); + assert.equal(functions.length, 2); + assert.ok(functions.every((fn) => fn.comments.length)); + assert.equal( + functionsIn( + 'View.svelte', + '

Hi

', + ).length, + 1, + ); + assert.throws( + () => functionsIn('broken.ts', 'function f( {'), + /Cannot parse/, + ); +}); + +test('documentation fingerprint preserves array holes and changes to callback ownership', () => { + const signature = (source) => functionsIn('src/a.ts', source)[0].signature; + assert.equal( + signature('function f() { return [1]; }'), + signature('function f() { return [1,]; }'), + ); + assert.notEqual( + signature('function f() { return []; }'), + signature('function f() { return [,]; }'), + ); + assert.notEqual( + signature('function f() { return [1]; }'), + signature('function f() { return [1,,]; }'), + ); + assert.notEqual( + functionsIn('src/a.ts', 'first(() => { return 1; });')[0].key, + functionsIn('src/a.ts', 'second(() => { return 1; });')[0].key, + ); +}); + +test('whole-file line limits keep their identity when the diagnostic moves between functions', () => { + const finding = (line) => + eslintRecords([ + { + filePath: 'lib/guards.mjs', + messages: [ + { + ruleId: 'max-lines', + message: 'File has too many lines (600). Maximum allowed is 500.', + line, + column: 1, + }, + ], + }, + ])[0]; + assert.equal(finding(20).key, 'lib/guards.mjs|max-lines||0'); + assert.equal(finding(120).key, finding(20).key); +}); + +test('suppression gates do not mistake strings for directives', () => { + assert.equal( + suppressionErrors('a.ts', 'const example = "// @ts-ignore";', {}).length, + 0, + ); + assert.equal( + suppressionErrors('a.ts', '// @ts-nocheck\nexport const a = 1;', {}).length, + 1, + ); + assert.equal( + suppressionErrors('a.ts', '/* eslint-disable */\nexport const a = 1;', {}) + .length, + 1, + ); + assert.equal( + suppressionErrors( + 'a.ts', + '// eslint-disable-next-line no-alert -- Temporary browser compatibility\nalert(1);', + {}, + ).length, + 0, + ); + assert.equal( + suppressionErrors( + 'a.ts', + '// @ts-expect-error: Verify rejected primitive input\nf(1);', + {}, + ).length, + 1, + ); + assert.equal( + suppressionErrors( + 'negative.test.ts', + '// @ts-expect-error: Verify rejected primitive input\nf(1);', + { negativeTypeTests: ['*.test.ts'] }, + ).length, + 0, + ); +}); diff --git a/tools/quality/tsconfig.next.json b/tools/quality/tsconfig.next.json new file mode 100644 index 0000000..583277d --- /dev/null +++ b/tools/quality/tsconfig.next.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "noEmit": true, + "isolatedModules": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noEmitOnError": true, + "plugins": [{ "name": "next" }] + } +} diff --git a/tools/quality/tsconfig.node.json b/tools/quality/tsconfig.node.json new file mode 100644 index 0000000..cba7925 --- /dev/null +++ b/tools/quality/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noEmitOnError": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/tools/quality/tsconfig.svelte.json b/tools/quality/tsconfig.svelte.json new file mode 100644 index 0000000..da3596a --- /dev/null +++ b/tools/quality/tsconfig.svelte.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "isolatedModules": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noEmitOnError": true + } +} From 12765aa49a2d2040544ac5f8c7b06e8c5bb45d96 Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Mon, 12 Oct 2026 20:13:45 +0500 Subject: [PATCH 2/9] fix: keep local quality dependencies out of source control --- .gitignore | 3 +++ tools/quality/node_modules | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 120000 tools/quality/node_modules diff --git a/.gitignore b/.gitignore index f5a35ed..6915625 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ node_modules/ quality/reports/ .husky/_/ + +# Local shared-tool dependency links are never repository content. +tools/quality/node_modules diff --git a/tools/quality/node_modules b/tools/quality/node_modules deleted file mode 120000 index 0c2f3cd..0000000 --- a/tools/quality/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/jannur/WebstormProjects/1msg-quality-156-20261012/shared-quality-template/node_modules \ No newline at end of file From c793f383dc345fc0978d1a0fe171f1a1c4015884 Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Mon, 12 Oct 2026 20:21:20 +0500 Subject: [PATCH 3/9] fix: verify incoming SDK bytes before adopting generation manifests --- .github/workflows/quality.yml | 2 +- quality/generator/generate.mjs | 12 ++++++++---- quality/generator/verify-negative.mjs | 24 +++++++++++++----------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index cb86db2..7de2d74 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -36,7 +36,7 @@ jobs: - run: node quality/generator/verify-negative.mjs - uses: shivammathur/setup-php@v2 with: - php-version: "8.3" + php-version: "8.5.11" extensions: curl, mbstring tools: composer:2.8.12 - run: composer install --no-interaction --prefer-dist diff --git a/quality/generator/generate.mjs b/quality/generator/generate.mjs index d26a6a9..00da04d 100644 --- a/quality/generator/generate.mjs +++ b/quality/generator/generate.mjs @@ -128,14 +128,18 @@ try { } await writeFile(inventoryPath, JSON.stringify(expected, null, 2) + "\n"); } else { - for (const actual of [ - await inventory(root), - JSON.parse(await readFile(inventoryPath, "utf8")), - ]) + // Synchronization may advance only the manifest after independently verifying + // the incoming producer output; it must never overwrite differing bytes. + const candidates = [await inventory(root)]; + if (!process.argv.includes("--adopt")) + candidates.push(JSON.parse(await readFile(inventoryPath, "utf8"))); + for (const actual of candidates) if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error( "Generated drift: review producer then run node quality/generator/generate.mjs --write", ); + if (process.argv.includes("--adopt")) + await writeFile(inventoryPath, JSON.stringify(expected, null, 2) + "\n"); } console.log( `Verified ${Object.keys(expected).length} generated ${config.language} code paths`, diff --git a/quality/generator/verify-negative.mjs b/quality/generator/verify-negative.mjs index 24a22d5..fb950d2 100644 --- a/quality/generator/verify-negative.mjs +++ b/quality/generator/verify-negative.mjs @@ -18,17 +18,19 @@ const ignorePath = join(root, ".gitignore"); const originalIgnore = readFileSync(ignorePath); /** Require the real generation replay to fail specifically on output drift, not a missing tool or configuration. */ function rejectDrift() { - const result = spawnSync( - process.execPath, - ["quality/generator/generate.mjs"], - { cwd: root, encoding: "utf8", timeout: 120000 }, - ); - assert.notEqual(result.status, 0, "generation accepted modified output"); - assert.match( - result.stderr + result.stdout, - /Generated drift/, - "fixture failed for an unrelated reason", - ); + for (const flags of [[], ["--adopt"]]) { + const result = spawnSync( + process.execPath, + ["quality/generator/generate.mjs", ...flags], + { cwd: root, encoding: "utf8", timeout: 120000 }, + ); + assert.notEqual(result.status, 0, "generation accepted modified output"); + assert.match( + result.stderr + result.stdout, + /Generated drift/, + "fixture failed for an unrelated reason", + ); + } } try { writeFileSync( From ce9181ac6134931ae4eb12786516446105d4b78d Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Tue, 13 Oct 2026 00:24:10 +0500 Subject: [PATCH 4/9] ci(quality): update sealed analyzer identity checks to 1.0.8 --- tools/quality/README.md | 27 ++- tools/quality/canonical-duplicates.mjs | 128 +++++++++++++ tools/quality/complexity.mjs | 83 +++++++++ tools/quality/lib/guards.mjs | 74 +++++--- tools/quality/lib/historical.mjs | 2 +- tools/quality/lib/reports.mjs | 222 ++++++++++++++++++++-- tools/quality/package-lock.json | 4 +- tools/quality/package.json | 2 +- tools/quality/run.mjs | 8 + tools/quality/template-manifest.json | 24 ++- tools/quality/test/cli.test.mjs | 241 ++++++++++++++++++++++++ tools/quality/test/complexity.test.mjs | 247 +++++++++++++++++++++++++ tools/quality/test/ratchet.test.mjs | 28 ++- tools/quality/test/reports.test.mjs | 192 +++++++++++++++++++ 14 files changed, 1226 insertions(+), 56 deletions(-) create mode 100644 tools/quality/canonical-duplicates.mjs create mode 100644 tools/quality/complexity.mjs create mode 100644 tools/quality/test/complexity.test.mjs create mode 100644 tools/quality/test/reports.test.mjs diff --git a/tools/quality/README.md b/tools/quality/README.md index ebb38d7..4b600bd 100644 --- a/tools/quality/README.md +++ b/tools/quality/README.md @@ -1,4 +1,4 @@ -# 1MSG quality template 1.0.2 +# 1MSG quality template 1.0.8 Vendored, private template for 1MSG-156. Install its exact tools with `npm --prefix tools/quality ci` on Node 24.19.0. The lockfile and @@ -83,3 +83,28 @@ Integrity verification prevents hiding authored code under the vendor exemption. No analyzer flag may depend on CI or a skip-type-aware environment variable. Version 1.0.2 resolves Svelte embedded clone paths to their physical source while retaining fragment identity. Initial Knip replay omits only the exact current-only sealed vendor ignore; authored stale ignores remain errors. + +## Canonical maintainability measurement (1.0.3) + +For adoption that includes formatting, run `node tools/quality/complexity.mjs --config eslint.complexity.config.mjs` as the analyzer inside the existing `report --kind eslint --name complexity` gate. Both trusted historical source and current source are independently formatted in memory with the pinned Prettier version and sealed policy before the same ESLint rules execute. Files on disk are never rewritten. This gives line metrics the same unit of measurement; AST complexity and the per-finding ratchet remain enforced. The original Git source hash remains adoption evidence. The JSON report includes canonical source so diagnostic locations identify the correct function after formatting. Formatter/parse failures are fatal. This does not replace `Prettier --check` on the actual checkout. + +Existing consumers pinned to 1.0.2 do not need a policy change; 1.0.3 adds the explicit canonical measurement command. + +For JS/TS formatting migrations, `duplicateCanonicalFormat: true` measures both trusted base and current source after the same pinned formatter in disposable copies. Declare the supported `duplicateFormats` (`javascript`, `typescript`, `jsx`, `tsx`) and exact ignored dependency/generated paths. Source files stay unchanged. New/grown clone groups still fail even if the aggregate percentage falls. Unsupported framework source, unexpected symlinks, empty coverage or failed formatting are analyzer failures. Report line numbers refer to canonical source fragments. + +Version 1.0.6 runs canonical complexity with inline ESLint configuration disabled on both sides. Legacy directives for unrelated framework plugins cannot corrupt metric coverage, and a source comment cannot suppress complexity debt. The separate source suppression guard still enforces the current-code policy. Trusted replay also copies exact Jest config filenames as analyzer inputs, allowing Knip to load current test-runner configuration against historical application and test source. Ordinary source files continue to come exclusively from the trusted Git archive. + +Version 1.0.7 parses JS/TS/JSX/TSX with the pinned TypeScript compiler and prints an in-memory, comment-free metric input before pinned Prettier formatting. Comments inside JSX callbacks otherwise change indentation and wrap executable lines, inflating line debt despite `skipComments`. Both current and trusted historical sources use the same normalization; recompute both sides when adopting this version. No rule limits or baseline allowances change. The original files are not rewritten, and strings, regular expressions, template literals and rendered JSX content remain data rather than comment matches. Unsupported source extensions and syntax errors fail closed before recovered syntax can become a metric report. + +The 31-test suite includes actual CLI fixtures proving that JSX documentation leaves file/function line metrics unchanged, an added statement still increases those metrics, malformed JS/TSX fails, and canonical string/tagged-template/regular-expression, JSX rendering and automatic-semicolon behavior is preserved. JSX assertions compare rendered text and element order because formatting may split adjacent text nodes. Formatting and documentation guards still inspect the actual checkout. Consumers remaining on sealed 1.0.2 are unchanged by this release. + +## Version 1.0.8 + +Complexity findings use complete lexical ownership (class, function, binding, +property and call argument), independent of emitted diagnostic order. Fixing an +older callback therefore cannot renumber a different callback's allowance. +Ambiguous sibling scopes are tied to ordered, comment-free AST evidence; changed +or indistinguishable groups fail closed. The real CLI fixtures verify both +legitimate debt reduction and rejection of transferred/increased allowances. +Existing consumers must replay historical source with this version when adopting +the new key schema; old diagnostic ordinals are never silently migrated. diff --git a/tools/quality/canonical-duplicates.mjs b/tools/quality/canonical-duplicates.mjs new file mode 100644 index 0000000..c706926 --- /dev/null +++ b/tools/quality/canonical-duplicates.mjs @@ -0,0 +1,128 @@ +import { spawnSync } from 'node:child_process'; +import { + lstat, + mkdir, + mkdtemp, + readFile, + readdir, + rm, + writeFile, + realpath, +} from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { format } from 'prettier'; +import picomatch from 'picomatch'; +import policy from './prettier.config.mjs'; +const config = JSON.parse(process.argv[2]); +const root = process.cwd(); +if (!Array.isArray(config.duplicatePaths) || !config.duplicatePaths.length) + throw new Error('Canonical clone roots are required'); +for (const file of config.duplicatePaths) + if ( + typeof file !== 'string' || + file.startsWith('/') || + file.split(/[\\/]/).includes('..') + ) + throw new Error('Clone root must remain inside the repository'); +const formats = config.duplicateFormats ?? [ + 'javascript', + 'typescript', + 'jsx', + 'tsx', +]; +if ( + !formats.length || + formats.some( + (value) => !['javascript', 'typescript', 'jsx', 'tsx'].includes(value), + ) +) + throw new Error( + 'Canonical clone measurement supports only declared JavaScript and TypeScript formats', + ); +const temporary = await realpath( + await mkdtemp(join(tmpdir(), 'canonical-clones-')), +); +const ignored = picomatch(config.duplicateIgnore ?? [], { dot: true }); +let files = 0; +/** Inventory declared roots without following symlinks and format each authored source with the same sealed policy. */ +async function copy(file) { + if (ignored(file)) return; + const info = await lstat(join(root, file)); + if (info.isSymbolicLink()) throw Error(`Unreviewed source symlink: ${file}`); + if (info.isDirectory()) { + for (const entry of await readdir(join(root, file))) + await copy(join(file, entry)); + return; + } + if (/\.(svelte|vue)$/.test(file)) + throw new Error( + `Canonical clone measurement needs a framework formatter for ${file}`, + ); + if (!/\.[cm]?[jt]sx?$/.test(file)) return; + const canonical = await format(await readFile(join(root, file), 'utf8'), { + ...policy, + filepath: file, + }); + const target = join(temporary, file); + await mkdir(dirname(target), { recursive: true }); + await writeFile(target, canonical); + files++; +} +/** Restore portable source identities after analyzing the temporary canonical copy; fragments retain their measured text. */ +function originalPaths(value) { + if (typeof value === 'string' && value.startsWith(temporary + '/')) + return join(root, value.slice(temporary.length + 1)); + if (Array.isArray(value)) return value.map(originalPaths); + if (value && typeof value === 'object') + return Object.fromEntries( + Object.entries(value).map(([key, item]) => [ + originalPaths(key), + originalPaths(item), + ]), + ); + return value; +} +try { + for (const file of config.duplicatePaths) await copy(file); + if (!files) throw Error('Canonical clone inventory is empty'); + const tool = new URL('./node_modules/jscpd/run-jscpd.js', import.meta.url) + .pathname; + const result = spawnSync( + process.execPath, + [ + tool, + ...config.duplicatePaths, + '--reporters', + 'json', + '--output', + 'reports', + '--mode', + 'mild', + '--min-lines', + '5', + '--min-tokens', + '50', + '--silent', + '--absolute', + '--format', + formats.join(','), + ], + { + cwd: temporary, + encoding: 'utf8', + timeout: 180000, + maxBuffer: 50 * 1024 * 1024, + }, + ); + if (result.status !== 0 || result.error || result.signal) + throw Error(result.stderr || result.stdout || String(result.error)); + const report = JSON.parse( + await readFile(join(temporary, 'reports/jscpd-report.json'), 'utf8'), + ); + if (report.duplicates.some((clone) => typeof clone.fragment !== 'string')) + throw Error('Missing canonical clone fragment'); + process.stdout.write(JSON.stringify(originalPaths(report))); +} finally { + await rm(temporary, { recursive: true, force: true }); +} diff --git a/tools/quality/complexity.mjs b/tools/quality/complexity.mjs new file mode 100644 index 0000000..fb0b27a --- /dev/null +++ b/tools/quality/complexity.mjs @@ -0,0 +1,83 @@ +#!/usr/bin/env node +import { realpathSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { extname } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +import { ESLint } from 'eslint'; +import { format } from 'prettier'; +import ts from 'typescript'; + +import policy from './prettier.config.mjs'; + +/** Remove only parsed comments before formatting so documentation cannot inflate wrapped code lines; reject unsupported or invalid syntax. */ +function metricSource(source, filePath) { + if (!/^[.](?:[cm]?[jt]s|[jt]sx)$/.test(extname(filePath))) + throw new Error(`Unsupported complexity source: ${filePath}`); + const syntax = ts.createSourceFile( + filePath, + source, + ts.ScriptTarget.Latest, + true, + ); + if (syntax.parseDiagnostics.length) { + const diagnostic = syntax.parseDiagnostics[0]; + throw new Error( + `Complexity parse failed: ${filePath}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`, + ); + } + return ts + .createPrinter({ removeComments: true, newLine: ts.NewLineKind.LineFeed }) + .printFile(syntax); +} + +/** Measure comment-free source with the same pinned formatting policy, retaining canonical text for diagnostic scope identities. */ +export async function measureComplexity(config, targets) { + const eslint = new ESLint({ + overrideConfigFile: config, + allowInlineConfig: false, + }); + const inventory = await eslint.lintFiles(targets); + if (!inventory.length) + throw new Error('Complexity source inventory is empty'); + const reports = []; + for (const file of inventory) { + if (file.fatalErrorCount) + throw new Error(`Complexity parse failed: ${file.filePath}`); + const source = await readFile(file.filePath, 'utf8'); + const canonical = await format(metricSource(source, file.filePath), { + ...policy, + filepath: file.filePath, + }); + const [report] = await eslint.lintText(canonical, { + filePath: file.filePath, + }); + if (!report || report.fatalErrorCount) + throw new Error(`Complexity parse failed: ${file.filePath}`); + reports.push({ ...report, source: canonical }); + } + return reports; +} + +/** Keep formatter, parser and inventory failures distinct from ordinary maintainability diagnostics. */ +async function main() { + const args = process.argv.slice(2); + const position = args.indexOf('--config'); + if (position < 0 || !args[position + 1]) + throw new Error('Complexity measurement requires --config'); + const [config] = args.splice(position, 2).slice(1); + const reports = await measureComplexity(config, args.length ? args : ['.']); + process.stdout.write(JSON.stringify(reports)); + if (reports.some((report) => report.errorCount || report.warningCount)) + process.exitCode = 1; +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href +) { + main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 2; + }); +} diff --git a/tools/quality/lib/guards.mjs b/tools/quality/lib/guards.mjs index 500c8fe..f27c583 100644 --- a/tools/quality/lib/guards.mjs +++ b/tools/quality/lib/guards.mjs @@ -30,6 +30,11 @@ export function validateConfig( 'sourceRoots must inventory the authored source and tooling', ); } + if ( + config.duplicateCanonicalFormat !== undefined && + typeof config.duplicateCanonicalFormat !== 'boolean' + ) + throw new Error('duplicateCanonicalFormat must be an explicit boolean'); for (const root of config.sourceRoots) { if (!existsSync(root)) throw new Error(`Missing source root: ${root}`); } @@ -93,28 +98,32 @@ export function parseUnits(path, text) { ]; } -/** Compare semantic tokens so formatting/quote changes do not create a false method edit. */ -function functionFingerprint(fragment) { - const scanner = ts.createScanner( - ts.ScriptTarget.Latest, - true, - ts.LanguageVariant.JSX, - fragment, - ); +/** Compare parsed semantic tokens, including template segments, without treating comments or optional trailing commas as method edits. */ +function functionFingerprint(node, ast) { const tokens = []; - for ( - let token = scanner.scan(); - token !== ts.SyntaxKind.EndOfFileToken; - token = scanner.scan() - ) { - const value = [ - ts.SyntaxKind.StringLiteral, - ts.SyntaxKind.NoSubstitutionTemplateLiteral, - ].includes(token) - ? scanner.getTokenValue() - : scanner.getTokenText(); - tokens.push([token, value]); + /** Traverse lexical tokens rather than rescanning text, so template-expression context remains accurate. */ + function visit(current) { + if ( + current.kind >= ts.SyntaxKind.FirstJSDocNode && + current.kind <= ts.SyntaxKind.LastJSDocNode + ) + return; + const children = current.getChildren(ast); + if (children.length) { + for (const child of children) visit(child); + return; + } + if (current.kind === ts.SyntaxKind.SyntaxList) return; + const value = + ts.isStringLiteralLike(current) || + ts.isTemplateHead(current) || + ts.isTemplateMiddle(current) || + ts.isTemplateTail(current) + ? current.text + : current.getText(ast); + tokens.push([current.kind, value]); } + visit(node); const meaningful = tokens.filter(([kind], index) => { if (kind !== ts.SyntaxKind.CommaToken) return true; const next = tokens[index + 1]?.[0]; @@ -134,6 +143,27 @@ function functionFingerprint(fragment) { return digest(JSON.stringify(meaningful)); } +/** Identify a call chain without embedding mutable arguments or comments into callback ownership. */ +function lexicalCall(node, ast) { + if ( + ts.isParenthesizedExpression(node) || + ts.isAsExpression(node) || + ts.isNonNullExpression(node) + ) + return lexicalCall(node.expression, ast); + if (ts.isCallExpression(node) || ts.isNewExpression(node)) + return `${lexicalCall(node.expression, ast)}()`; + if (ts.isPropertyAccessExpression(node)) + return `${lexicalCall(node.expression, ast)}.${node.name.text}`; + if (ts.isElementAccessExpression(node)) { + const key = ts.isStringLiteralLike(node.argumentExpression) + ? node.argumentExpression.text + : ''; + return `${lexicalCall(node.expression, ast)}[${key}]`; + } + return ts.isIdentifier(node) ? node.text : ''; +} + /** Identify callbacks by their lexical owner, not their position among unrelated functions. */ function lexicalScope(node, ast) { const owners = []; @@ -142,7 +172,7 @@ function lexicalScope(node, ast) { const name = current.name ?? current.parent?.name; if (name) owners.push(name.getText(ast)); } else if (ts.isCallExpression(current)) { - owners.push(current.expression.getText(ast)); + owners.push(lexicalCall(current.expression, ast)); } } return owners.reverse().join('/'); @@ -212,7 +242,7 @@ export function functionsIn(path, text) { : []; functions.push({ key: `${suffix}:${lexicalScope(node, ast)}:${name}`, - signature: functionFingerprint(node.getText(ast)), + signature: functionFingerprint(node, ast), name, comments: [ ...comments, diff --git a/tools/quality/lib/historical.mjs b/tools/quality/lib/historical.mjs index 7ed6be8..29ba0a2 100644 --- a/tools/quality/lib/historical.mjs +++ b/tools/quality/lib/historical.mjs @@ -94,7 +94,7 @@ export function historicalAnalysis(base, config, templateDirectory, analyze) { cpSync('package.json', join(snapshot, 'package.json')); } const configuration = paths.filter((path) => - /(?:^|\/)(?:eslint(?:\.[\w-]+)?\.config\.[cm]?js|dependency-cruiser(?:\.[\w-]+)?\.config\.[cm]?js|tsconfig(?:\.[\w-]+)?\.json|quality\.config\.mjs)$/.test( + /(?:^|\/)(?:eslint(?:\.[\w-]+)?\.config\.[cm]?js|dependency-cruiser(?:\.[\w-]+)?\.config\.[cm]?js|jest(?:\.[\w-]+)?\.config\.[cm]?[jt]s|tsconfig(?:\.[\w-]+)?\.json|quality\.config\.mjs)$/.test( path, ), ); diff --git a/tools/quality/lib/reports.mjs b/tools/quality/lib/reports.mjs index 19c9a8e..b04abef 100644 --- a/tools/quality/lib/reports.mjs +++ b/tools/quality/lib/reports.mjs @@ -184,25 +184,200 @@ export function duplicateRecords(report) { .sort((a, b) => a.key.localeCompare(b.key)); } -/** Locate the enclosing declaration for an ESLint diagnostic without using unstable line numbers as identity. */ -function scopeAt(source, line, column) { +/** Fingerprint parsed syntax without comments, retaining literal and template text that can change callback behavior. */ +function syntaxFingerprint(node, source) { + const tokens = []; + /** Preserve parser token boundaries, including template substitutions, instead of rescanning a context-free fragment. */ + function visit(current) { + if ( + current.kind >= ts.SyntaxKind.FirstJSDocNode && + current.kind <= ts.SyntaxKind.LastJSDocNode + ) + return; + const children = current.getChildren(source); + if (children.length) { + for (const child of children) visit(child); + } else if (current.kind !== ts.SyntaxKind.SyntaxList) { + tokens.push([current.kind, current.getText(source)]); + } + } + visit(node); + return digest(JSON.stringify(tokens)); +} + +/** Identify a callable expression without incorporating the bodies or mutable arguments of other callbacks in its chain. */ +function callTarget(node, source) { + if ( + ts.isParenthesizedExpression(node) || + ts.isAsExpression(node) || + ts.isTypeAssertionExpression(node) || + ts.isNonNullExpression(node) || + ts.isSatisfiesExpression(node) + ) + return callTarget(node.expression, source); + if (ts.isIdentifier(node)) return ['identifier', node.text]; + if (node.kind === ts.SyntaxKind.ThisKeyword) return ['this']; + if (node.kind === ts.SyntaxKind.SuperKeyword) return ['super']; + if (ts.isPropertyAccessExpression(node)) + return ['member', callTarget(node.expression, source), node.name.text]; + if (ts.isElementAccessExpression(node)) + return [ + 'element', + callTarget(node.expression, source), + syntaxFingerprint(node.argumentExpression, source), + ]; + if (ts.isCallExpression(node) || ts.isNewExpression(node)) + return [ts.SyntaxKind[node.kind], callTarget(node.expression, source)]; + if (ts.isFunctionLike(node)) + return [ + ts.SyntaxKind[node.kind], + node.name?.getText(source) ?? '', + ]; + return ['expression', syntaxFingerprint(node, source)]; +} + +/** Keep ordinary binding names readable while preserving computed and destructured names without formatting-sensitive text. */ +function ownerName(node, source) { + if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text; + if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node)) + return ['literal', node.text]; + return ['computed', syntaxFingerprint(node, source)]; +} + +/** Include every named lexical owner and each enclosing call's argument position, without using source lines or diagnostic order. */ +function lexicalOwners(node, source) { + const owners = []; + let child; + for ( + let current = node; + current && !ts.isSourceFile(current); + current = current.parent + ) { + if (ts.isCallExpression(current) || ts.isNewExpression(current)) { + const argument = current.arguments?.indexOf(child) ?? -1; + owners.push([ + ts.SyntaxKind[current.kind], + callTarget(current.expression, source), + argument < 0 ? 'callee' : argument, + ]); + } else if ( + ts.isBinaryExpression(current) && + current.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + current.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) { + owners.push(['assignment', callTarget(current.left, source)]); + } else if (ts.isFunctionLike(current) || ts.isClassLike(current)) { + owners.push([ + ts.SyntaxKind[current.kind], + current.name ? ownerName(current.name, source) : '', + current.modifiers?.some( + (modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword, + ) + ? 'static' + : 'instance', + ]); + } else if (current.name) { + owners.push([ + ts.SyntaxKind[current.kind], + ownerName(current.name, source), + ]); + } else if ( + ts.isJsxOpeningElement(current) || + ts.isJsxSelfClosingElement(current) + ) { + owners.push(['jsx', current.tagName.getText(source)]); + } + child = current; + } + return owners.reverse(); +} + +/** Recognize independently reported nesting nodes; sibling blocks must not share a function's depth allowance. */ +function isDepthScope(node) { + return [ + ts.SyntaxKind.IfStatement, + ts.SyntaxKind.ForStatement, + ts.SyntaxKind.ForInStatement, + ts.SyntaxKind.ForOfStatement, + ts.SyntaxKind.WhileStatement, + ts.SyntaxKind.DoStatement, + ts.SyntaxKind.SwitchStatement, + ts.SyntaxKind.TryStatement, + ts.SyntaxKind.WithStatement, + ].includes(node.kind); +} + +/** Index all lexical scopes, including functions below lint thresholds, so disappearing diagnostics cannot renumber another allowance. */ +function scopeIdentities(source, depth = false) { + const groups = new Map(); + /** Inventory complete syntax before resolving any diagnostic, keeping below-threshold siblings in ambiguity decisions. */ + function visit(node) { + const included = depth + ? isDepthScope(node) + : (ts.isFunctionLike(node) && node.body) || + ts.isClassStaticBlockDeclaration(node) || + (ts.isPropertyDeclaration(node) && node.initializer); + if (included) { + const owners = lexicalOwners(node, source); + if (depth) owners.push(['nesting', ts.SyntaxKind[node.kind]]); + if (ts.isClassStaticBlockDeclaration(node)) owners.push(['static-block']); + const owner = JSON.stringify(owners); + const group = groups.get(owner) ?? []; + group.push(node); + groups.set(owner, group); + } + ts.forEachChild(node, visit); + } + visit(source); + const identities = new Map(); + for (const [owner, nodes] of groups) { + if (nodes.length === 1) { + identities.set(nodes[0], owner); + continue; + } + const fingerprints = nodes.map((node) => syntaxFingerprint(node, source)); + // Preserve source order in ambiguous groups: swapping a large and small body must not transfer the large allowance. + const group = digest(JSON.stringify(fingerprints)); + for (const [index, node] of nodes.entries()) { + const fingerprint = fingerprints[index]; + // A changed ambiguous group loses every old allowance; even matching another old body cannot borrow its budget. + identities.set( + node, + fingerprints.indexOf(fingerprint) === + fingerprints.lastIndexOf(fingerprint) + ? `${owner}|group:${group}|member:${fingerprint}` + : null, + ); + } + } + return identities; +} + +/** Resolve a diagnostic to its innermost inventoried owner; indistinguishable siblings and missing locations fail closed. */ +function scopeAt(source, identities, line, column) { const offset = source.getPositionOfLineAndCharacter( Math.max(0, line - 1), Math.max(0, column - 1), ); - let scope = ''; - const visit = (node) => { - if (node.pos <= offset && node.end >= offset) { - if (ts.isFunctionLike(node)) - scope = - node.name?.getText(source) ?? - node.parent.name?.getText(source) ?? - ''; + let matched; + /** Select the innermost scope containing the reported token, never an unrelated neighboring declaration. */ + function visit(node) { + if (node.getStart(source) <= offset && node.end > offset) { + if (identities.has(node)) matched = node; ts.forEachChild(node, visit); } - }; + } visit(source); - return scope; + if (!matched) + throw new Error( + `No lexical scope for ESLint diagnostic: ${source.fileName}:${line}:${column}`, + ); + const identity = identities.get(matched); + if (identity === null) + throw new Error( + `Ambiguous lexical scope for ESLint diagnostic: ${source.fileName}:${line}:${column}`, + ); + return identity; } /** Only complexity diagnostics may use a debt baseline; parser, typed and correctness errors remain fatal. */ @@ -221,13 +396,18 @@ export function eslintRecords(report) { for (const file of report) { const path = repoPath(file.filePath); if (!Array.isArray(file.messages)) throw new Error('Invalid ESLint report'); + if (file.messages.length === 0) continue; const source = ts.createSourceFile( path, - currentFile(path) ?? '', + file.source ?? currentFile(path) ?? '', ts.ScriptTarget.Latest, true, ); - const occurrences = new Map(); + if (source.parseDiagnostics.length) + throw new Error(`Invalid syntax in ESLint scope evidence: ${path}`); + const functions = scopeIdentities(source); + const depths = scopeIdentities(source, true); + const identities = new Set(); for (const message of file.messages) { if (message.fatal || !allowed.has(message.ruleId)) { throw new Error( @@ -248,12 +428,18 @@ export function eslintRecords(report) { const scope = message.ruleId === 'max-lines' ? '' - : scopeAt(source, message.line ?? 1, message.column ?? 1); + : scopeAt( + source, + message.ruleId === 'max-depth' ? depths : functions, + message.line ?? 1, + message.column ?? 1, + ); const identity = `${path}|${message.ruleId}|${scope}`; - const occurrence = occurrences.get(identity) ?? 0; - occurrences.set(identity, occurrence + 1); + if (identities.has(identity)) + throw new Error(`Duplicate ESLint diagnostic identity: ${identity}`); + identities.add(identity); results.push( - record(`${identity}|${occurrence}`, Number(values[1]), { + record(identity, Number(values[1]), { path, rule: message.ruleId, scope, diff --git a/tools/quality/package-lock.json b/tools/quality/package-lock.json index 5b96b4b..8ad5519 100644 --- a/tools/quality/package-lock.json +++ b/tools/quality/package-lock.json @@ -1,12 +1,12 @@ { "name": "@1msg/quality-template", - "version": "1.0.2", + "version": "1.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@1msg/quality-template", - "version": "1.0.2", + "version": "1.0.8", "dependencies": { "@eslint/js": "9.39.5", "dependency-cruiser": "18.6.0", diff --git a/tools/quality/package.json b/tools/quality/package.json index 62d20c2..b71d5b0 100644 --- a/tools/quality/package.json +++ b/tools/quality/package.json @@ -1,6 +1,6 @@ { "name": "@1msg/quality-template", - "version": "1.0.2", + "version": "1.0.8", "private": true, "type": "module", "engines": { diff --git a/tools/quality/run.mjs b/tools/quality/run.mjs index e0b21ae..17e3c74 100644 --- a/tools/quality/run.mjs +++ b/tools/quality/run.mjs @@ -69,6 +69,14 @@ function unused(config, production) { function duplicates(config) { if (!config.duplicatePaths?.length) throw new Error('duplicatePaths must list authored runtime/tooling roots'); + if (config.duplicateCanonicalFormat) { + const { report } = analyze( + process.execPath, + [join(directory, 'canonical-duplicates.mjs'), JSON.stringify(config)], + 'Canonical jscpd', + ); + return { findings: duplicateRecords(report), report }; + } const output = mkdtempSync(join(tmpdir(), '1msg-jscpd-')); try { const argv = [ diff --git a/tools/quality/template-manifest.json b/tools/quality/template-manifest.json index 444e17d..da93308 100644 --- a/tools/quality/template-manifest.json +++ b/tools/quality/template-manifest.json @@ -1,23 +1,27 @@ { "schemaVersion": 1, - "version": "1.0.2", + "version": "1.0.8", "files": { - "README.md": "1d3f6f9d420adea402d92aff483c14271e6e24c291437d6ea5b59c3162702a20", + "README.md": "e74940b5acec1bdbff75eb751432967630ddb4215af24d89d31dd3694916c12e", + "canonical-duplicates.mjs": "a3583fdc3e344af448ea8d2543c2398daaa03fa10a0739da486afb04e35425ba", + "complexity.mjs": "4ed5553c392621e0615a48c12e12300c8602ed2a28cfbaff67002acf82e49cac", "eslint.mjs": "607ddfaac3d365fedd7dbc5e21c781d448347b1c36662f3492e4364ff9f22ded", "lib/baseline.mjs": "7876ba90b80448d9c9003c21e348095ed22630c41fc26ed944984742020549dd", "lib/git.mjs": "b7c26871526d864bf8d12f01c613d3bfa0adb2da9d072544d8f09407ef598758", - "lib/guards.mjs": "310c224c5d072fa16b1cf5f0c47e5da4258f0f63fa9a1c4dc114c9fe192bc398", - "lib/historical.mjs": "d338d88bae6d1da19f27296fbb6c57cff4ca09664fe7baa8d6c8d801ca3e69f2", + "lib/guards.mjs": "e94dc6b58703b44420f8b815ee069b9e0a7fa9aefd5faba63d55f1c3da7c4286", + "lib/historical.mjs": "43b46395828daa3f19d8692e9bd22ea6cfc1d0dbac83814d727894f672a0ddd2", "lib/knip-reporter.mjs": "175d7d96e2913e8f6de04b6c706172258d9d7654a26ba7f9ed7edff79d3fc8ca", - "lib/reports.mjs": "f47f6cbf8022691e61a245bf54a5fdd1ba9f2037906ec6576f2ac771224161dc", + "lib/reports.mjs": "60e78d58256d1060067e939397c8760384f202625c394f73d18e3311ccabb44c", "lib/template.mjs": "c2a14b715f0c9dbe8b8243b142f4c6628967400fb8f92767537169e1b7bfd8f4", - "package-lock.json": "931d28ffff6590892a072569df9c57345d0a388a22598b3482dee53136a52828", - "package.json": "b2c70073c423135e4b5abba9507d58fd5136a676339e816b72d6dd4bcbb1b757", + "package-lock.json": "fc4279a0f1393109958815b6b92fc95acd1c5a98387ea6d3f13a61a9aeba8a60", + "package.json": "3e9ef4c922c64deddd6c2c50350def2bf79cd0354af891e5d7051c082c4d4937", "prettier.config.mjs": "7576563a9a30aff8eb3177f9cc2e4bc7a0f6b825042eadb9a619b86f519c5e5e", - "run.mjs": "b60c0fc2419c8cfd32eddf369da38b2d87e2b7eacbc31fda31c598930900f199", + "run.mjs": "7cf26128cdf63f3a7ccab7341a0a274a352fd9bda0521ba41e37216bb9cc249f", "seal.mjs": "fb38c8e08ea3cf9f4dcd95e45ffc931aea3b7a54181fed88d7379f8aad4c050d", - "test/cli.test.mjs": "f743a18f0763cf5b5f42f8d2a4b71f67a4de900a3955a299309395af95b99cf3", - "test/ratchet.test.mjs": "62fef1cc7a1851db81c30966909968ff84637b8ed8003821120269c4fae70d49", + "test/cli.test.mjs": "64dda5d60ef9fd2d444f96b2623b97f6197917f1e121aaf73cbe1dfeff90a8ef", + "test/complexity.test.mjs": "ba1f702ba94de3fcad93cb5e43a49bc2456be1426c81754149420d210f2b3952", + "test/ratchet.test.mjs": "324a932c5a52d1b037e111042e1b41e5686f8d4dbfd0307faf86cee649ebc27a", + "test/reports.test.mjs": "d75a93ee4dc606d9930c3a1e19912e093e8635910a7c21f81c735e209c03835f", "tsconfig.next.json": "d900a35fcb0852376b06854df5be2f8f178e26cb5383b93b07ebeb6220e30faf", "tsconfig.node.json": "627661abcb7e992c4bfaf98d6e68a12678573b5d4f6e821ea0fa43fa6e233f28", "tsconfig.svelte.json": "5777247ea98aaf2d886b87cd12206a641377edcdd086ce5fd5b58adc6f0bc771" diff --git a/tools/quality/test/cli.test.mjs b/tools/quality/test/cli.test.mjs index 897d9cd..624b8ec 100644 --- a/tools/quality/test/cli.test.mjs +++ b/tools/quality/test/cli.test.mjs @@ -508,3 +508,244 @@ test('historical Knip removes a current-only exact vendor ignore without relaxin f.close(); } }); + +/** Pinned formatting defines identical clone units for old and current source, while a new participant still fails the real gate. */ +test('canonical jscpd CLI preserves formatting-only debt and rejects a new clone', () => { + const f = fixture(); + try { + f.write( + 'quality.config.mjs', + `export default { schemaVersion: 1, profile: 'A', defaultBranch: 'main', sourceRoots: ['src'], duplicatePaths: ['src'], duplicateCanonicalFormat: true, duplicateFormats: ['javascript', 'typescript', 'jsx', 'tsx'], duplicateIgnore: [], duplicateMaxPercentage: 100 };\n`, + ); + const source = + 'export function calculate() { const label = "ready"; let value = 0; ' + + Array.from({ length: 20 }, (_, i) => `value += ${i};`).join('') + + 'return { label, value }; }'; + f.write('src/a.js', source); + f.write('src/b.js', source); + const base = f.commit(); + const initial = f.run( + 'duplicates', + '--base', + base, + '--bootstrap', + '--owner', + 'quality-fixture', + '--expires', + '2099-12-31', + '--reason', + 'Historical clone fixture', + '--issue', + 'https://example.invalid/quality', + ); + assert.equal(initial.status, 0, initial.stderr); + const baseline = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/duplicates.json'), 'utf8'), + ); + assert.ok(baseline.entries.length > 0); + f.write( + 'src/a.js', + source.replaceAll(';', ';\n').replaceAll('"ready"', "'ready'"), + ); + const formatted = f.run('duplicates', '--base', base); + assert.equal(formatted.status, 0, formatted.stderr); + f.write('src/c.js', source); + const growth = f.run('duplicates', '--base', base); + assert.notEqual(growth.status, 0); + assert.match(growth.stderr, /New or increased|new\/increased/); + } finally { + f.close(); + } +}); + +/** Invalid syntax or a framework file without its formatter must fail canonical analysis instead of disappearing from coverage. */ +test('canonical jscpd fails closed for unsupported or malformed source', () => { + const f = fixture(); + try { + f.write( + 'quality.config.mjs', + `export default { schemaVersion: 1, profile: 'A', defaultBranch: 'main', sourceRoots: ['src'], duplicatePaths: ['src'], duplicateCanonicalFormat: true, duplicateIgnore: [], duplicateMaxPercentage: 100 };\n`, + ); + f.write('src/index.js', 'export const value = 1;'); + const base = f.commit(); + f.write('src/index.js', 'export const value = ;'); + assert.notEqual(f.run('duplicates', '--base', base).status, 0); + f.write('src/index.js', 'export const value = 1;'); + f.write( + 'src/Widget.svelte', + '

{value}

', + ); + const unsupported = f.run('duplicates', '--base', base); + assert.notEqual(unsupported.status, 0); + assert.match(unsupported.stderr, /framework formatter/); + } finally { + f.close(); + } +}); + +/** Current test-runner config is an analyzer input; historical application and test source must still come only from Git. */ +test('historical replay uses current Jest config without replacing trusted source', () => { + const f = fixture(); + try { + f.write('src/index.js', 'export const value = "historical";\n'); + f.write('jest.config.cjs', 'module.exports = { rootDir: "old" };\n'); + const base = f.commit(); + f.write('src/index.js', 'export const value = "current-only";\n'); + f.write('jest.config.cjs', 'module.exports = { rootDir: __dirname };\n'); + const historical = new URL('../lib/historical.mjs', import.meta.url).href; + const template = fileURLToPath(new URL('../', import.meta.url)); + const script = `import { historicalAnalysis } from ${JSON.stringify(historical)}; + import { readFileSync, existsSync } from 'node:fs'; + const value = historicalAnalysis(${JSON.stringify(base)}, {knipConfig:'knip.json'}, ${JSON.stringify(template)}, () => ({ + config: readFileSync('jest.config.cjs','utf8'), + source: readFileSync('src/index.js','utf8'), + newSource: existsSync('src/current-only.js') + })); process.stdout.write(JSON.stringify(value));`; + f.write( + 'src/current-only.js', + 'throw new Error("must not enter historical source");\n', + ); + const result = spawnSync( + process.execPath, + ['--input-type=module', '-e', script], + { cwd: f.cwd, encoding: 'utf8', timeout: 15000 }, + ); + assert.equal(result.status, 0, result.stderr); + const replay = JSON.parse(result.stdout); + assert.match(replay.config, /__dirname/); + assert.match(replay.source, /historical/); + assert.equal(replay.newSource, false); + } finally { + f.close(); + } +}); + +/** A fixed early callback cannot renumber later debt or lend its larger allowance to a different growing callback. */ +test('actual complexity CLI binds callback allowances to lexical owners', () => { + const f = fixture(); + try { + const preset = new URL('../eslint.mjs', import.meta.url).href; + f.write( + 'eslint.config.mjs', + `import { maintainabilityConfig, tseslint } from '${preset}'; export default [{languageOptions:{parser:tseslint.parser}}, ...maintainabilityConfig(), {rules:{complexity:['error',2]}}];`, + ); + const earlier = + 'const earlier = wrap((value) => { if (value.a) return 1; if (value.b) return 2; if (value.c) return 3; if (value.d) return 4; if (value.e) return 5; return 0; });'; + const later = + 'const later = wrap((value) => { if (value.a) return 1; if (value.b) return 2; return 0; });'; + f.write('src/index.js', `${earlier}\n${later}`); + const base = f.commit(); + const args = [ + 'report', + '--kind', + 'eslint', + '--base', + base, + '--bootstrap', + '--owner', + 'quality-fixture', + '--expires', + '2099-12-31', + '--reason', + 'Historical callback debt', + '--issue', + 'TEST-1', + '--', + process.execPath, + fileURLToPath(new URL('../complexity.mjs', import.meta.url)), + '--config', + 'eslint.config.mjs', + 'src/index.js', + ]; + const original = f.run(...args); + assert.equal(original.status, 0, original.stderr); + const originalBaseline = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.deepEqual( + originalBaseline.entries.map(({ value }) => value).sort(), + [3, 6], + ); + f.write( + 'src/index.js', + `const earlier = wrap((value) => value);\n${later}`, + ); + const fixed = f.run(...args); + assert.equal(fixed.status, 0, fixed.stderr); + const reducedBaseline = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.equal(reducedBaseline.entries.length, 1); + assert.equal( + reducedBaseline.entries[0].key, + originalBaseline.entries.find(({ value }) => value === 3).key, + ); + f.write( + 'src/index.js', + `const earlier = wrap((value) => value);\n${later.replace('return 0;', 'if (value.c) return 3; return 0;')}`, + ); + const growth = f.run(...args); + assert.equal(growth.status, 1, growth.stderr); + assert.match( + growth.stderr, + /Cannot baseline a new\/increased finding.*later/, + ); + } finally { + f.close(); + } +}); + +/** Class field initializers and static blocks are independent ESLint code paths and must keep their class ownership. */ +test('actual complexity CLI resolves class field and static block scopes', () => { + const f = fixture(); + try { + const preset = new URL('../eslint.mjs', import.meta.url).href; + f.write( + 'eslint.config.mjs', + `import { maintainabilityConfig, tseslint } from '${preset}'; export default [{languageOptions:{parser:tseslint.parser}}, ...maintainabilityConfig(), {rules:{complexity:['error',2]}}];`, + ); + f.write( + 'src/index.js', + `class First { + value = ready ? (active ? 1 : 2) : 3; + static { if (ready) start(); if (active) finish(); } + } + class Second { + value = ready ? (active ? 1 : 2) : 3; + static { if (ready) start(); if (active) finish(); } + }`, + ); + const base = f.commit(); + const result = f.run( + 'report', + '--kind', + 'eslint', + '--base', + base, + '--bootstrap', + '--owner', + 'quality-fixture', + '--expires', + '2099-12-31', + '--reason', + 'Historical class initializer debt', + '--issue', + 'TEST-1', + '--', + process.execPath, + fileURLToPath(new URL('../complexity.mjs', import.meta.url)), + '--config', + 'eslint.config.mjs', + 'src/index.js', + ); + assert.equal(result.status, 0, result.stderr); + const baseline = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.equal(baseline.entries.length, 4); + assert.equal(new Set(baseline.entries.map(({ key }) => key)).size, 4); + assert.ok(baseline.entries.every(({ value }) => value === 3)); + } finally { + f.close(); + } +}); diff --git a/tools/quality/test/complexity.test.mjs b/tools/quality/test/complexity.test.mjs new file mode 100644 index 0000000..ccf0730 --- /dev/null +++ b/tools/quality/test/complexity.test.mjs @@ -0,0 +1,247 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { + mkdtempSync, + writeFileSync, + readFileSync, + rmSync, + symlinkSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { runInNewContext } from 'node:vm'; + +import ts from 'typescript'; + +/** Run the shipped CLI against a disposable source file with real maintainability rules. */ +function fixture(t) { + const root = mkdtempSync(join(tmpdir(), 'canonical-complexity-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + const preset = new URL('../eslint.mjs', import.meta.url).href; + writeFileSync( + join(root, 'eslint.config.mjs'), + `import { maintainabilityConfig, tseslint } from '${preset}'; export default [{languageOptions:{parser:tseslint.parser}}, ...maintainabilityConfig()];`, + ); + const cli = join(root, 'quality-complexity.mjs'); + symlinkSync( + fileURLToPath(new URL('../complexity.mjs', import.meta.url)), + cli, + ); + /** Preserve analyzer exit status and JSON rather than treating a missing report as an empty finding list. */ + return (source, fileName = 'source.js') => { + writeFileSync(join(root, fileName), source); + const result = spawnSync( + process.execPath, + [cli, '--config', 'eslint.config.mjs', fileName], + { cwd: root, encoding: 'utf8', timeout: 15000 }, + ); + assert.equal(readFileSync(join(root, fileName), 'utf8'), source); + return result; + }; +} + +/** Whitespace-only growth is neutral, while an actual extra statement still increases the measured function. */ +test('canonical CLI uses identical units for historical and current line debt', (t) => { + const run = fixture(t); + const statements = Array.from({ length: 80 }, () => 'value += 1;'); + const compact = `export function count() { let value = 0; ${statements.join('')} return value; }`; + const expanded = `export function count() {\n\nlet value = 0;\n${statements.join('\n\n')}\nreturn value;\n}`; + const original = run(compact), + whitespace = run(expanded); + assert.equal(original.status, 1, original.stderr); + assert.equal(whitespace.status, 1, whitespace.stderr); + const [before] = JSON.parse(original.stdout), + [after] = JSON.parse(whitespace.stdout); + assert.equal( + before.source + .split('\n') + .filter((line) => line.trim()) + .join('\n'), + after.source + .split('\n') + .filter((line) => line.trim()) + .join('\n'), + ); + assert.deepEqual( + before.messages.map(({ ruleId, message }) => ({ ruleId, message })), + after.messages.map(({ ruleId, message }) => ({ ruleId, message })), + ); + const growth = run( + compact.replace('return value;', 'value += 2; return value;'), + ); + assert.equal(growth.status, 1, growth.stderr); + const [increased] = JSON.parse(growth.stdout); + assert.notDeepEqual(increased.messages, before.messages); + assert.ok( + increased.source.split('\n').length > before.source.split('\n').length, + ); +}); + +/** A formatter/parser error is a failed analyzer, never a zero-debt report or a baselinable diagnostic. */ +test('canonical CLI fails closed for invalid syntax', (t) => { + const result = fixture(t)('export function broken( {'); + assert.equal(result.status, 2); + assert.equal(result.stdout, ''); + assert.ok(result.stderr.length > 0); +}); + +/** Source directives cannot hide measured debt, and unrelated historical framework directives cannot become parser diagnostics. */ +test('canonical CLI ignores inline configuration on both metric inputs', (t) => { + const run = fixture(t); + const body = `export function count() { let value = 0; ${Array.from({ length: 80 }, () => 'value += 1;').join('')} return value; }`; + const plain = run(body); + const annotated = run( + '/* eslint-disable unknown-framework/legacy-rule, max-lines-per-function */\n' + + body, + ); + assert.equal(plain.status, 1, plain.stderr); + assert.equal(annotated.status, 1, annotated.stderr); + const select = (result) => + JSON.parse(result.stdout).flatMap((file) => + file.messages.map(({ ruleId, message }) => ({ ruleId, message })), + ); + assert.deepEqual(select(annotated), select(plain)); + assert.ok( + select(annotated).some((item) => item.ruleId === 'max-lines-per-function'), + ); +}); + +/** Execute canonical TS/JSX through the same compiler settings to compare literals, child text and evaluation order. */ +function evaluate(source, fileName) { + const compiled = ts.transpileModule(source, { + fileName, + compilerOptions: { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.None, + jsx: ts.JsxEmit.React, + jsxFactory: 'element', + }, + }); + const context = {}; + runInNewContext(compiled.outputText, context, { timeout: 1000 }); + return JSON.parse(context.result); +} + +/** Callback documentation and JSX comments cannot change file/function line debt; actual instructions must still grow it. */ +test('canonical CLI strips JSX documentation before measuring wrapped line debt', (t) => { + const run = fixture(t); + const statements = Array.from({ length: 160 }, () => 'value += 1;').join( + '\n', + ); + const declarations = Array.from( + { length: 350 }, + (_, index) => `export const field${index} = ${index};`, + ).join('\n'); + const plain = `export function Card() { + let value = 0; + ${statements} + return
{field.label}
} />; + } + ${declarations}`; + const documented = plain + .replace( + 'export function', + '/** Render the editable panel and preserve its accumulated count. */\nexport function', + ) + .replace( + 'render={', + 'render={/** Provide the field label and accessible description to the panel. */ ', + ) + .replace( + '', + '{/* Keep the visible label inside the field container. */}', + ) + .replace( + 'value += 1;', + 'value += /* Increment the visible count once. */ 1;', + ); + const original = run(plain, 'source.tsx'); + const annotated = run(documented, 'source.tsx'); + assert.equal(original.status, 1, original.stderr); + assert.equal(annotated.status, 1, annotated.stderr); + const [before] = JSON.parse(original.stdout); + const [after] = JSON.parse(annotated.stdout); + assert.equal(after.source, before.source); + assert.deepEqual(after.messages, before.messages); + assert.ok(before.messages.some(({ ruleId }) => ruleId === 'max-lines')); + assert.ok( + before.messages.some(({ ruleId }) => ruleId === 'max-lines-per-function'), + ); + const growth = run( + documented.replace('let value = 0;', 'let value = 0; value += 2;'), + 'source.tsx', + ); + assert.equal(growth.status, 1, growth.stderr); + const [increased] = JSON.parse(growth.stdout); + assert.notDeepEqual( + increased.messages.map(({ ruleId, message }) => ({ ruleId, message })), + before.messages.map(({ ruleId, message }) => ({ ruleId, message })), + ); +}); + +/** Comment-like literals and rendered JSX text/element order survive printing; adjacent text nodes are normalized like DOM content. */ +test('canonical CLI preserves string, tagged-template and JSX semantics', (t) => { + const source = [ + `function element(type, props, ...children) { + const rendered = []; + for (const child of children) { + const previous = rendered.length - 1; + if (typeof child === 'string' && typeof rendered[previous] === 'string') rendered[previous] += child; + else rendered.push(child); + } + return {type, props, children: rendered}; + }`, + 'const value = "https://example.invalid/a/*literal*/";', + 'const raw = String.raw`line\\n/* literal */ ${value} // end`;', + 'const cooked = `first\nsecond ${value}`;', + String.raw`const expression = /\/\*literal\*\//u;`, + 'const node =
leading {/* actual comment */}{value} trailing
;', + 'globalThis.result = JSON.stringify({value, raw, cooked, matches: expression.test(value), node});', + ].join('\n'); + const result = fixture(t)(source, 'source.tsx'); + assert.equal(result.status, 0, result.stderr); + const [report] = JSON.parse(result.stdout); + assert.deepEqual( + evaluate(report.source, 'source.tsx'), + evaluate(source, 'source.tsx'), + ); + assert.equal( + evaluate(report.source, 'source.tsx').node.props.title, + '/* attribute */', + ); + assert.match(evaluate(report.source, 'source.tsx').raw, /line\\n/); +}); + +/** Comments near automatic semicolon insertion and adjacent unary operators cannot change executable statements. */ +test('canonical CLI preserves automatic-semicolon and operator semantics', (t) => { + const source = `function returned() { + return /* Preserve the original newline after return. */ + 42; + } + const sum = 1 + /* Keep the unary plus separate. */ +2; + globalThis.result = JSON.stringify({isUndefined: returned() === undefined, sum});`; + const result = fixture(t)(source); + assert.equal(result.status, 0, result.stderr); + const [report] = JSON.parse(result.stdout); + assert.deepEqual(evaluate(report.source, 'source.js'), { + isUndefined: true, + sum: 3, + }); + assert.deepEqual( + evaluate(report.source, 'source.js'), + evaluate(source, 'source.js'), + ); +}); + +/** Invalid TSX remains an analyzer failure even though the TypeScript printer can emit a recovered syntax tree. */ +test('canonical CLI fails closed before printing a recovered TSX tree', (t) => { + const result = fixture(t)( + 'export const view =
{/* valid comment */}
;', + 'source.tsx', + ); + assert.equal(result.status, 2); + assert.equal(result.stdout, ''); + assert.match(result.stderr, /Complexity parse failed/); +}); diff --git a/tools/quality/test/ratchet.test.mjs b/tools/quality/test/ratchet.test.mjs index 7722924..fc2376c 100644 --- a/tools/quality/test/ratchet.test.mjs +++ b/tools/quality/test/ratchet.test.mjs @@ -193,7 +193,7 @@ test('whole-file line limits keep their identity when the diagnostic moves betwe ], }, ])[0]; - assert.equal(finding(20).key, 'lib/guards.mjs|max-lines||0'); + assert.equal(finding(20).key, 'lib/guards.mjs|max-lines|'); assert.equal(finding(120).key, finding(20).key); }); @@ -236,3 +236,29 @@ test('suppression gates do not mistake strings for directives', () => { 0, ); }); + +/** Formatting within template expressions and a chained call must not invent a new function or callback. */ +test('documentation identities preserve template-expression context and stable call chains', () => { + const before = + 'describe("groupIntoBlocks",()=>{it("splits a parent",()=>{const value=Array.from({length:3},(_,i)=>({key:`p.k${i}`,text:`Text ${i}`}));return value;});});'; + const after = + "describe('groupIntoBlocks', () => {\n it('splits a parent', () => {\n const value = Array.from({length:3}, (_, i) => ({\n key: `p.k${i}`,\n text: `Text ${i}`,\n })); return value;\n });\n});"; + const identity = (source) => + functionsIn('src/a.ts', source).map(({ key, signature }) => ({ + key, + signature, + })); + assert.deepEqual(identity(before), identity(after)); + assert.notDeepEqual( + identity(before), + identity(before.replace('Text ${i}', 'Different ${i}')), + ); + const chained = 'prefetch({ value: 1 }).catch(() => { recover(); });'; + const documented = + 'prefetch({ /** Explain the input. */ value: 1, }).catch(() => { recover(); });'; + assert.deepEqual(identity(chained), identity(documented)); + assert.notDeepEqual( + identity(chained), + identity(chained.replace('prefetch', 'send')), + ); +}); diff --git a/tools/quality/test/reports.test.mjs b/tools/quality/test/reports.test.mjs new file mode 100644 index 0000000..68c39e2 --- /dev/null +++ b/tools/quality/test/reports.test.mjs @@ -0,0 +1,192 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { ESLint } from 'eslint'; + +import { eslintRecords } from '../lib/reports.mjs'; + +/** Generate real small-fixture diagnostics with low thresholds without changing the shipped policy. */ +async function analyze(source, rule = 'complexity', limit = 2) { + const eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + { + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, + rules: { [rule]: ['error', limit] }, + }, + ], + }); + const reports = await eslint.lintText(source, { filePath: 'fixture.js' }); + assert.equal(reports.length, 1); + assert.equal(reports[0].fatalErrorCount, 0); + return reports.map((report) => ({ + ...report, + filePath: 'fixture.js', + source, + })); +} + +/** Vary callback bodies while retaining their call site to probe allowance transfer rather than report counts. */ +function callback(branches, result = '0') { + return `(value) => { ${Array.from({ length: branches }, (_, index) => `if (value.p${index}) return ${index + 1};`).join(' ')} return ${result}; }`; +} + +/** Resolve the real analyzer report to stable keys, preserving parser and ambiguity failures. */ +async function findings(source, rule, limit) { + return eslintRecords(await analyze(source, rule, limit)); +} + +/** Variables and object/JSX properties name anonymous callbacks even when their containing call is unchanged. */ +test('callback identities retain all named variable and property owners', async () => { + const before = `const first = wrap(${callback(2)}); const second = wrap(${callback(3)}); + const options = { load: wrap(${callback(2)}), save: wrap(${callback(3)}) }; + const view = ;`; + const original = await findings(before); + assert.equal(original.length, 6); + assert.equal(new Set(original.map(({ key }) => key)).size, 6); + const current = await findings(before.replaceAll(callback(2), callback(0))); + assert.equal(current.length, 3); + assert.deepEqual( + current, + original.filter(({ value }) => value === 4), + ); +}); + +/** Method names alone cannot move allowances across class, instance/static, or enclosing function boundaries. */ +test('same method and nested function names keep class and function ownership', async () => { + const before = `class First { handle(value) { if (value.a) return 1; if (value.b) return 2; return 0; } + static handle(value) { if (value.a) return 1; if (value.b) return 2; if (value.c) return 3; return 0; } } + class Second { handle(value) { if (value.a) return 1; if (value.b) return 2; if (value.c) return 3; return 0; } } + function left() { function nested(value) { if (value.a) return 1; if (value.b) return 2; return 0; } return nested; } + function right() { function nested(value) { if (value.a) return 1; if (value.b) return 2; if (value.c) return 3; return 0; } return nested; }`; + const original = await findings(before); + assert.equal(original.length, 5); + const reduced = before.replaceAll( + 'if (value.a) return 1; if (value.b) return 2; return 0;', + 'return value;', + ); + assert.deepEqual( + await findings(reduced), + original.filter(({ value }) => value === 4), + ); +}); + +/** The call target and argument index distinguish callbacks without depending on another diagnostic's presence or order. */ +test('call targets and argument positions distinguish unnamed callbacks', async () => { + const source = `left.accept(${callback(2)}, ${callback(3)}); right.accept(${callback(2)});`; + const report = await analyze(source); + const original = eslintRecords(report); + assert.equal(original.length, 3); + const reordered = report.map((file) => ({ + ...file, + messages: [...file.messages].reverse(), + })); + assert.deepEqual(eslintRecords(reordered), original); + const reduced = source.replace( + `left.accept(${callback(2)},`, + `left.accept(${callback(0)},`, + ); + assert.deepEqual( + await findings(reduced), + original.filter( + ({ key, value }) => !(key.includes('"left"') && value === 3), + ), + ); +}); + +/** Identical caller/name chains require the whole sibling group as evidence, including callbacks currently below the limit. */ +test('ambiguous groups cannot borrow another callback body allowance', async () => { + const before = `run(${callback(5)}); run(${callback(2)});`; + const oldKeys = new Set((await findings(before)).map(({ key }) => key)); + const after = `run(${callback(0)}); run(${callback(5)});`; + const current = await findings(after); + assert.equal(current.length, 1); + assert.ok(current.every(({ key }) => !oldKeys.has(key))); + const belowThreshold = `run(${callback(0)}); run(${callback(2)});`; + const prior = await findings(belowThreshold); + const changedSibling = await findings( + `run(${callback(1)}); run(${callback(2)});`, + ); + assert.notEqual(prior[0].key, changedSibling[0].key); + const originalOrder = await findings( + `run(${callback(5)}); run(${callback(0)});`, + ); + const swappedBodies = await findings( + `run(${callback(0)}); run(${callback(5)});`, + ); + assert.notEqual(originalOrder[0].key, swappedBodies[0].key); +}); + +/** Formatting and comments leave ambiguous syntax fingerprints stable; literal/template/JSX content remains meaningful evidence. */ +test('ambiguous fingerprints preserve source semantics and ignore comments', async () => { + const first = callback(2, '`first ${value.name}`'); + const second = callback(2, 'second'); + const source = `run(${first}); run(${second});`; + const original = await findings(source); + assert.equal(original.length, 2); + assert.deepEqual( + await findings(source.replaceAll('if (', '/* Condition */\nif (')), + original, + ); + const changed = await findings( + source.replace('first ${value.name}', 'changed ${value.name}'), + ); + assert.ok( + changed.every(({ key }) => !original.some((entry) => entry.key === key)), + ); + const jsxChanged = await findings(source.replace('>second<', '>different<')); + assert.ok( + jsxChanged.every(({ key }) => !original.some((entry) => entry.key === key)), + ); +}); + +/** Equal bodies under the same lexical owner have no trustworthy correspondence, and duplicate reports cannot invent an ordinal fallback. */ +test('indistinguishable callbacks and duplicate diagnostics fail closed', async () => { + await assert.rejects( + () => findings(`run(${callback(2)}); run(${callback(2)});`), + /Ambiguous lexical scope/, + ); + const reports = await analyze(`const handler = wrap(${callback(2)});`); + reports[0].messages.push(reports[0].messages[0]); + assert.throws( + () => eslintRecords(reports), + /Duplicate ESLint diagnostic identity/, + ); +}); + +/** Separate deep branches have independent syntax owners, so a function cannot lend its deepest block's allowance to another block. */ +test('nested depth reports are keyed by syntax instead of diagnostic ordinal', async () => { + const source = + 'function check(value) { if (value.a) { if (value.b) { if (value.c) return 1; } } if (value.d) { if (value.e) { if (value.f) { if (value.g) return 2; } } } }'; + const records = await findings(source, 'max-depth', 2); + assert.ok(records.length > 1); + assert.equal(new Set(records.map(({ key }) => key)).size, records.length); +}); + +/** Recovered ASTs and missing function locations cannot silently attach findings to file-level debt. */ +test('invalid or missing scope evidence fails closed', () => { + const message = { + ruleId: 'complexity', + message: 'Arrow function has a complexity of 3. Maximum allowed is 2.', + line: 1, + column: 1, + }; + assert.throws( + () => + eslintRecords([ + { filePath: 'missing.js', source: '', messages: [message] }, + ]), + /No lexical scope/, + ); + assert.throws( + () => + eslintRecords([ + { + filePath: 'invalid.js', + source: 'function broken( {', + messages: [message], + }, + ]), + /Invalid syntax/, + ); +}); From 4f77ab7e4090f7439362245d7c91ab76bd3b01e4 Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Tue, 13 Oct 2026 01:03:05 +0500 Subject: [PATCH 5/9] ci(quality): seal lexical scope regression fixes --- tools/quality/README.md | 12 ++++- tools/quality/lib/reports.mjs | 26 +++++++++- tools/quality/package-lock.json | 4 +- tools/quality/package.json | 2 +- tools/quality/template-manifest.json | 14 +++--- tools/quality/test/cli.test.mjs | 74 ++++++++++++++++++++++++++++ tools/quality/test/reports.test.mjs | 46 +++++++++++++++++ 7 files changed, 165 insertions(+), 13 deletions(-) diff --git a/tools/quality/README.md b/tools/quality/README.md index 4b600bd..aa67e5b 100644 --- a/tools/quality/README.md +++ b/tools/quality/README.md @@ -1,4 +1,4 @@ -# 1MSG quality template 1.0.8 +# 1MSG quality template 1.0.9 Vendored, private template for 1MSG-156. Install its exact tools with `npm --prefix tools/quality ci` on Node 24.19.0. The lockfile and @@ -108,3 +108,13 @@ or indistinguishable groups fail closed. The real CLI fixtures verify both legitimate debt reduction and rejection of transferred/increased allowances. Existing consumers must replay historical source with this version when adopting the new key schema; old diagnostic ordinals are never silently migrated. + +## 1.0.9 migration + +Complexity identities now retain lexical `for`, `for-in` and `for-of` headers +(binding, source/condition/increment and `await` mode). Identical statements in +different loops no longer collide. Loop bodies, source lines and diagnostic +ordinals do not identify allowances; indistinguishable sibling loops still fail +closed. Consumers must replay their original trusted adoption base with the +updated analyzer, preserving debt owners, deadlines and reasons. Do not translate +baseline keys manually or renew historical debt as part of the version update. diff --git a/tools/quality/lib/reports.mjs b/tools/quality/lib/reports.mjs index b04abef..b70336f 100644 --- a/tools/quality/lib/reports.mjs +++ b/tools/quality/lib/reports.mjs @@ -244,7 +244,7 @@ function ownerName(node, source) { return ['computed', syntaxFingerprint(node, source)]; } -/** Include every named lexical owner and each enclosing call's argument position, without using source lines or diagnostic order. */ +/** Include named owners, lexical loop bindings and call argument positions without source lines or diagnostic order. */ function lexicalOwners(node, source) { const owners = []; let child; @@ -253,7 +253,29 @@ function lexicalOwners(node, source) { current && !ts.isSourceFile(current); current = current.parent ) { - if (ts.isCallExpression(current) || ts.isNewExpression(current)) { + if (ts.isForOfStatement(current) || ts.isForInStatement(current)) { + owners.push([ + ts.SyntaxKind[current.kind], + syntaxFingerprint(current.initializer, source), + syntaxFingerprint(current.expression, source), + ts.isForOfStatement(current) && current.awaitModifier + ? 'await' + : 'sync', + child === current.statement ? 'body' : 'header', + ]); + } else if (ts.isForStatement(current)) { + owners.push([ + ts.SyntaxKind[current.kind], + current.initializer + ? syntaxFingerprint(current.initializer, source) + : null, + current.condition ? syntaxFingerprint(current.condition, source) : null, + current.incrementor + ? syntaxFingerprint(current.incrementor, source) + : null, + child === current.statement ? 'body' : 'header', + ]); + } else if (ts.isCallExpression(current) || ts.isNewExpression(current)) { const argument = current.arguments?.indexOf(child) ?? -1; owners.push([ ts.SyntaxKind[current.kind], diff --git a/tools/quality/package-lock.json b/tools/quality/package-lock.json index 8ad5519..0c6a588 100644 --- a/tools/quality/package-lock.json +++ b/tools/quality/package-lock.json @@ -1,12 +1,12 @@ { "name": "@1msg/quality-template", - "version": "1.0.8", + "version": "1.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@1msg/quality-template", - "version": "1.0.8", + "version": "1.0.9", "dependencies": { "@eslint/js": "9.39.5", "dependency-cruiser": "18.6.0", diff --git a/tools/quality/package.json b/tools/quality/package.json index b71d5b0..0620fb9 100644 --- a/tools/quality/package.json +++ b/tools/quality/package.json @@ -1,6 +1,6 @@ { "name": "@1msg/quality-template", - "version": "1.0.8", + "version": "1.0.9", "private": true, "type": "module", "engines": { diff --git a/tools/quality/template-manifest.json b/tools/quality/template-manifest.json index da93308..883a990 100644 --- a/tools/quality/template-manifest.json +++ b/tools/quality/template-manifest.json @@ -1,8 +1,8 @@ { "schemaVersion": 1, - "version": "1.0.8", + "version": "1.0.9", "files": { - "README.md": "e74940b5acec1bdbff75eb751432967630ddb4215af24d89d31dd3694916c12e", + "README.md": "91dd028828d08424d26dd4f5fc1e91da5c2258a3604a2fab57658c0f1c59b745", "canonical-duplicates.mjs": "a3583fdc3e344af448ea8d2543c2398daaa03fa10a0739da486afb04e35425ba", "complexity.mjs": "4ed5553c392621e0615a48c12e12300c8602ed2a28cfbaff67002acf82e49cac", "eslint.mjs": "607ddfaac3d365fedd7dbc5e21c781d448347b1c36662f3492e4364ff9f22ded", @@ -11,17 +11,17 @@ "lib/guards.mjs": "e94dc6b58703b44420f8b815ee069b9e0a7fa9aefd5faba63d55f1c3da7c4286", "lib/historical.mjs": "43b46395828daa3f19d8692e9bd22ea6cfc1d0dbac83814d727894f672a0ddd2", "lib/knip-reporter.mjs": "175d7d96e2913e8f6de04b6c706172258d9d7654a26ba7f9ed7edff79d3fc8ca", - "lib/reports.mjs": "60e78d58256d1060067e939397c8760384f202625c394f73d18e3311ccabb44c", + "lib/reports.mjs": "e41c4994e444128ae0418eff7240f49de678a97112fa7d5171fbe680d26b6dff", "lib/template.mjs": "c2a14b715f0c9dbe8b8243b142f4c6628967400fb8f92767537169e1b7bfd8f4", - "package-lock.json": "fc4279a0f1393109958815b6b92fc95acd1c5a98387ea6d3f13a61a9aeba8a60", - "package.json": "3e9ef4c922c64deddd6c2c50350def2bf79cd0354af891e5d7051c082c4d4937", + "package-lock.json": "314de2cb801a00bb42240da3567274bac9725359c0429665c505653a51bf5f91", + "package.json": "e1228b0144b1d8bd79283bfacec0be9a54f636e973ba42de4b1cc3694972f57e", "prettier.config.mjs": "7576563a9a30aff8eb3177f9cc2e4bc7a0f6b825042eadb9a619b86f519c5e5e", "run.mjs": "7cf26128cdf63f3a7ccab7341a0a274a352fd9bda0521ba41e37216bb9cc249f", "seal.mjs": "fb38c8e08ea3cf9f4dcd95e45ffc931aea3b7a54181fed88d7379f8aad4c050d", - "test/cli.test.mjs": "64dda5d60ef9fd2d444f96b2623b97f6197917f1e121aaf73cbe1dfeff90a8ef", + "test/cli.test.mjs": "63350dcf9c58c0e41d0dcc49bb8b9eef8dd0c01e781f25a58490f1476f58e94a", "test/complexity.test.mjs": "ba1f702ba94de3fcad93cb5e43a49bc2456be1426c81754149420d210f2b3952", "test/ratchet.test.mjs": "324a932c5a52d1b037e111042e1b41e5686f8d4dbfd0307faf86cee649ebc27a", - "test/reports.test.mjs": "d75a93ee4dc606d9930c3a1e19912e093e8635910a7c21f81c735e209c03835f", + "test/reports.test.mjs": "3d5be0312d4870dd00e4cdafaa422d1e0d4e0d3ecaf4dc65a73a4dac577bfbfa", "tsconfig.next.json": "d900a35fcb0852376b06854df5be2f8f178e26cb5383b93b07ebeb6220e30faf", "tsconfig.node.json": "627661abcb7e992c4bfaf98d6e68a12678573b5d4f6e821ea0fa43fa6e233f28", "tsconfig.svelte.json": "5777247ea98aaf2d886b87cd12206a641377edcdd086ce5fd5b58adc6f0bc771" diff --git a/tools/quality/test/cli.test.mjs b/tools/quality/test/cli.test.mjs index 624b8ec..33425a7 100644 --- a/tools/quality/test/cli.test.mjs +++ b/tools/quality/test/cli.test.mjs @@ -749,3 +749,77 @@ test('actual complexity CLI resolves class field and static block scopes', () => f.close(); } }); + +/** A Make-shaped source has equal if statements in separate loops; trusted replay retains only the precise old depth allowance. */ +test('actual raw complexity CLI distinguishes lexical loops and rejects depth transfer', () => { + const f = fixture(); + try { + f.write( + 'eslint.config.mjs', + "export default [{rules:{'max-depth':['error',3]}}];", + ); + const shallow = + 'for (const ack of root.ack) { const item = record(ack); if (item) found.push(statusEvent(item)); }'; + const deep = + 'for (const entry of root.entry) { for (const change of entry.changes) { for (const status of change.statuses) { const item = record(status); if (item) found.push(statusEvent(item)); } } }'; + f.write('src/index.js', `function collect(root) { ${shallow} ${deep} }`); + const base = f.commit(); + const args = [ + 'report', + '--kind', + 'eslint', + '--base', + base, + '--bootstrap', + '--owner', + 'quality-fixture', + '--expires', + '2099-12-31', + '--reason', + 'Historical lexical loop depth', + '--issue', + 'TEST-1', + '--', + process.execPath, + fileURLToPath( + new URL('../node_modules/eslint/bin/eslint.js', import.meta.url), + ), + '--config', + 'eslint.config.mjs', + '--no-inline-config', + '--format', + 'json', + 'src/index.js', + ]; + const original = f.run(...args); + assert.equal(original.status, 0, original.stderr); + const old = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.equal(old.entries.length, 1); + assert.equal(old.entries[0].value, 4); + f.write('src/index.js', `function collect(root) { ${deep} }`); + const reduced = f.run(...args); + assert.equal(reduced.status, 0, reduced.stderr); + const retained = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.equal(retained.entries[0].key, old.entries[0].key); + f.write( + 'src/index.js', + `function collect(root) { if (root.ready) { if (root.active) { ${shallow} } } }`, + ); + const transfer = f.run(...args); + assert.equal(transfer.status, 1, transfer.stderr); + assert.match(transfer.stderr, /Cannot baseline a new\/increased finding/); + f.write( + 'src/index.js', + `function collect(root) { if (root.ready) { ${deep} } }`, + ); + const growth = f.run(...args); + assert.equal(growth.status, 1, growth.stderr); + assert.match(growth.stderr, /Cannot baseline a new\/increased finding/); + } finally { + f.close(); + } +}); diff --git a/tools/quality/test/reports.test.mjs b/tools/quality/test/reports.test.mjs index 68c39e2..053a7df 100644 --- a/tools/quality/test/reports.test.mjs +++ b/tools/quality/test/reports.test.mjs @@ -190,3 +190,49 @@ test('invalid or missing scope evidence fails closed', () => { /Invalid syntax/, ); }); + +/** Equal statements under distinct lexical loop bindings are independently addressable even when only one is over threshold. */ +test('loop headers distinguish nesting and callback owners without body ordinals', async () => { + const source = `function collect(root) { + for (const ack of root.ack) { const item = record(ack); if (item) found.push(statusEvent(item)); } + for (const entry of root.entry) { for (const change of entry.changes) { + for (const status of change.statuses) { const item = record(status); if (item) found.push(statusEvent(item)); } + } } + }`; + const original = await findings(source, 'max-depth', 3); + assert.equal(original.length, 1); + assert.equal(original[0].value, 4); + assert.deepEqual( + await findings( + source.replace('const ack', '/* Header documentation */ const ack'), + 'max-depth', + 3, + ), + original, + ); + const callbacks = `for (const ack of root.ack) { run(${callback(2)}); } + for (const status of root.statuses) { run(${callback(2)}); } + for (const key in root.properties) { run(${callback(2)}); } + for (let index = 0; index < root.length; index++) { run(${callback(2)}); }`; + const owned = await findings(callbacks); + assert.equal(owned.length, 4); + assert.equal(new Set(owned.map(({ key }) => key)).size, 4); + const asynchronous = await findings( + `for await (const ack of root.ack) { run(${callback(2)}); }`, + ); + assert.equal(asynchronous.length, 1); + assert.ok(!owned.some(({ key }) => key === asynchronous[0].key)); + const shifted = await findings( + callbacks.replace('const ack of root.ack', 'const other of root.ack'), + ); + assert.equal( + shifted.filter(({ key }) => !owned.some((old) => old.key === key)).length, + 1, + ); +}); + +/** Repeated loops with identical headers remain ambiguous; distinct bindings must never enable ordinal-based allowance borrowing. */ +test('identical loop bindings do not disambiguate indistinguishable callbacks', async () => { + const loop = `for (const item of root.items) { run(${callback(2)}); }`; + await assert.rejects(() => findings(loop + loop), /Ambiguous lexical scope/); +}); From c88a2ba4e0c69cc65eaba9bb9568d722df0bdc8b Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Tue, 13 Oct 2026 01:53:46 +0500 Subject: [PATCH 6/9] chore(quality): adopt sealed analyzer 1.0.10 --- tools/quality/README.md | 21 +++- tools/quality/lib/guards.mjs | 18 +++- tools/quality/lib/reports.mjs | 110 ++++++++++++++++++++- tools/quality/package-lock.json | 4 +- tools/quality/package.json | 2 +- tools/quality/template-manifest.json | 17 ++-- tools/quality/test/cli.test.mjs | 80 +++++++++++++++ tools/quality/test/guard-history.test.mjs | 113 ++++++++++++++++++++++ tools/quality/test/reports.test.mjs | 106 ++++++++++++++++++++ 9 files changed, 456 insertions(+), 15 deletions(-) create mode 100644 tools/quality/test/guard-history.test.mjs diff --git a/tools/quality/README.md b/tools/quality/README.md index aa67e5b..2e4def1 100644 --- a/tools/quality/README.md +++ b/tools/quality/README.md @@ -1,4 +1,4 @@ -# 1MSG quality template 1.0.9 +# 1MSG quality template 1.0.10 Vendored, private template for 1MSG-156. Install its exact tools with `npm --prefix tools/quality ci` on Node 24.19.0. The lockfile and @@ -118,3 +118,22 @@ ordinals do not identify allowances; indistinguishable sibling loops still fail closed. Consumers must replay their original trusted adoption base with the updated analyzer, preserving debt owners, deadlines and reasons. Do not translate baseline keys manually or renew historical debt as part of the version update. + +## 1.0.10 migration + +Otherwise identical sibling scopes use the nearest distinguishing parsed branch or +block context, including then/else, loop, switch and try/catch/finally roles. This +fallback leaves already unambiguous identities unchanged. The complete ordered +peer evidence remains part of an ambiguous group's identity; swaps, new peers, +growth and fully indistinguishable ancestry cannot borrow another allowance. +ESLint object-property diagnostics resolve the exact property name to its direct +callable initializer. Previously misattributed nested callback metrics now belong +to that callback, rather than its enclosing function. Consumers must replay their +original trusted adoption base; preserve owners, expiry and reasons and never +translate baseline keys manually. + +For function documentation only, a syntax error in trusted historical source +removes all inherited function signatures: every current function in that file +must have documentation. Current syntax errors and historical/current I/O, +configuration or Git failures remain fatal. This conservative documentation +fallback does not alter metric analysis, source coverage or debt baselines. diff --git a/tools/quality/lib/guards.mjs b/tools/quality/lib/guards.mjs index f27c583..69e3bde 100644 --- a/tools/quality/lib/guards.mjs +++ b/tools/quality/lib/guards.mjs @@ -211,12 +211,15 @@ function callbackComments(node, ast) { return comments; } +/** Identify source syntax failures without conflating analyzer, configuration or I/O errors. */ +class SourceParseError extends SyntaxError {} + /** Extract all implemented functions, including private methods, factories and substantive callbacks. */ export function functionsIn(path, text) { const functions = []; for (const { suffix, ast } of parseUnits(path, text)) { if (ast.parseDiagnostics.length) - throw new Error( + throw new SourceParseError( `Cannot parse ${path}: ${ast.parseDiagnostics[0].messageText}`, ); const visit = (node) => { @@ -266,6 +269,17 @@ export function functionsIn(path, text) { return functions; } +/** Invalid historical syntax earns no unchanged-function allowance; every current function must satisfy the documentation contract. */ +function historicalFunctions(path, text) { + if (text === null) return []; + try { + return functionsIn(path, text); + } catch (error) { + if (error instanceof SourceParseError) return []; + throw error; + } +} + /** Identify suppression directives in comments only, never in strings or generated documentation examples. */ export function suppressionErrors(path, text, config) { const errors = []; @@ -358,7 +372,7 @@ export function runGuards(config, base) { continue; const before = baseFile(base, path); const previous = new Map(); - for (const fn of before === null ? [] : functionsIn(path, before)) { + for (const fn of historicalFunctions(path, before)) { const identity = `${fn.key}:${fn.signature}`; previous.set(identity, (previous.get(identity) ?? 0) + 1); } diff --git a/tools/quality/lib/reports.mjs b/tools/quality/lib/reports.mjs index b70336f..4cec53b 100644 --- a/tools/quality/lib/reports.mjs +++ b/tools/quality/lib/reports.mjs @@ -329,6 +329,92 @@ function isDepthScope(node) { ].includes(node.kind); } +/** Identify a control-flow child by its semantic role, never its position among statements. */ +function branchRole(parent, child) { + if (ts.isIfStatement(parent) || ts.isConditionalExpression(parent)) { + if (child === (parent.thenStatement ?? parent.whenTrue)) return 'then'; + if (child === (parent.elseStatement ?? parent.whenFalse)) return 'else'; + return 'condition'; + } + if (ts.isTryStatement(parent)) { + if (child === parent.tryBlock) return 'try'; + if (child === parent.catchClause) return 'catch'; + return 'finally'; + } + if (ts.isCatchClause(parent)) + return child === parent.block ? 'body' : 'binding'; + if (ts.isCaseClause(parent)) + return child === parent.expression ? 'case-value' : 'case-body'; + if (ts.isDefaultClause(parent)) return 'default-body'; + if (ts.isSwitchStatement(parent)) + return child === parent.caseBlock ? 'cases' : 'expression'; + if (isDepthScope(parent)) + return child === parent.statement ? 'body' : 'header'; + return 'block'; +} + +/** Collect parsed enclosing branches only for otherwise indistinguishable peers; literal values and then/else roles remain evidence. */ +function structuralContext(node, source) { + const contexts = []; + let child = node; + for ( + let parent = node.parent; + parent && !ts.isSourceFile(parent); + parent = parent.parent + ) { + if ( + ts.isBlock(parent) || + isDepthScope(parent) || + ts.isConditionalExpression(parent) || + ts.isCatchClause(parent) || + ts.isCaseClause(parent) || + ts.isDefaultClause(parent) + ) { + contexts.push([ + ts.SyntaxKind[parent.kind], + branchRole(parent, child), + syntaxFingerprint(parent, source), + ]); + } + child = parent; + } + return contexts; +} + +/** Find the shortest unique ancestor prefix among equal bodies; completely equal ancestry still has no safe correspondence. */ +function distinctContexts(nodes, fingerprints, source) { + const contexts = nodes.map( + /** Avoid introducing contextual identities for a body already distinguished by its own syntax. */ + (node, index) => + fingerprints.indexOf(fingerprints[index]) === + fingerprints.lastIndexOf(fingerprints[index]) + ? [] + : structuralContext(node, source), + ); + return contexts.map( + /** Select the shortest semantic ancestor evidence that separates this body from its equal peers. */ ( + context, + index, + ) => { + const peers = fingerprints.flatMap((fingerprint, peer) => + fingerprint === fingerprints[index] && peer !== index ? [peer] : [], + ); + if (!peers.length) return null; + for (let length = 1; length <= context.length; length++) { + const candidate = JSON.stringify(context.slice(0, length)); + if ( + peers.every( + (peer) => + JSON.stringify(contexts[peer].slice(0, length)) !== candidate, + ) + ) + return candidate; + } + return null; + }, + ); +} + /** Index all lexical scopes, including functions below lint thresholds, so disappearing diagnostics cannot renumber another allowance. */ function scopeIdentities(source, depth = false) { const groups = new Map(); @@ -360,6 +446,15 @@ function scopeIdentities(source, depth = false) { const fingerprints = nodes.map((node) => syntaxFingerprint(node, source)); // Preserve source order in ambiguous groups: swapping a large and small body must not transfer the large allowance. const group = digest(JSON.stringify(fingerprints)); + const contexts = distinctContexts(nodes, fingerprints, source); + const contextualGroup = digest( + JSON.stringify( + fingerprints.map((fingerprint, index) => [ + fingerprint, + contexts[index], + ]), + ), + ); for (const [index, node] of nodes.entries()) { const fingerprint = fingerprints[index]; // A changed ambiguous group loses every old allowance; even matching another old body cannot borrow its budget. @@ -368,7 +463,9 @@ function scopeIdentities(source, depth = false) { fingerprints.indexOf(fingerprint) === fingerprints.lastIndexOf(fingerprint) ? `${owner}|group:${group}|member:${fingerprint}` - : null, + : contexts[index] === null + ? null + : `${owner}|context-group:${contextualGroup}|member:${fingerprint}|context:${digest(contexts[index])}`, ); } } @@ -386,6 +483,17 @@ function scopeAt(source, identities, line, column) { function visit(node) { if (node.getStart(source) <= offset && node.end > offset) { if (identities.has(node)) matched = node; + // ESLint complexity locates a named object property before its arrow/function initializer. + // Resolve only that direct callable initializer, never an unrelated neighboring function. + if ( + ts.isPropertyAssignment(node) && + ts.isFunctionLike(node.initializer) && + identities.has(node.initializer) && + offset >= node.name.getStart(source) && + offset < node.name.end + ) { + matched = node.initializer; + } ts.forEachChild(node, visit); } } diff --git a/tools/quality/package-lock.json b/tools/quality/package-lock.json index 0c6a588..5e6bab1 100644 --- a/tools/quality/package-lock.json +++ b/tools/quality/package-lock.json @@ -1,12 +1,12 @@ { "name": "@1msg/quality-template", - "version": "1.0.9", + "version": "1.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@1msg/quality-template", - "version": "1.0.9", + "version": "1.0.10", "dependencies": { "@eslint/js": "9.39.5", "dependency-cruiser": "18.6.0", diff --git a/tools/quality/package.json b/tools/quality/package.json index 0620fb9..07e7cfe 100644 --- a/tools/quality/package.json +++ b/tools/quality/package.json @@ -1,6 +1,6 @@ { "name": "@1msg/quality-template", - "version": "1.0.9", + "version": "1.0.10", "private": true, "type": "module", "engines": { diff --git a/tools/quality/template-manifest.json b/tools/quality/template-manifest.json index 883a990..f2cf559 100644 --- a/tools/quality/template-manifest.json +++ b/tools/quality/template-manifest.json @@ -1,27 +1,28 @@ { "schemaVersion": 1, - "version": "1.0.9", + "version": "1.0.10", "files": { - "README.md": "91dd028828d08424d26dd4f5fc1e91da5c2258a3604a2fab57658c0f1c59b745", + "README.md": "50df5c60db609380718c9228de8f8f53cf1949673bd66f71a9f0192fae1d6dbd", "canonical-duplicates.mjs": "a3583fdc3e344af448ea8d2543c2398daaa03fa10a0739da486afb04e35425ba", "complexity.mjs": "4ed5553c392621e0615a48c12e12300c8602ed2a28cfbaff67002acf82e49cac", "eslint.mjs": "607ddfaac3d365fedd7dbc5e21c781d448347b1c36662f3492e4364ff9f22ded", "lib/baseline.mjs": "7876ba90b80448d9c9003c21e348095ed22630c41fc26ed944984742020549dd", "lib/git.mjs": "b7c26871526d864bf8d12f01c613d3bfa0adb2da9d072544d8f09407ef598758", - "lib/guards.mjs": "e94dc6b58703b44420f8b815ee069b9e0a7fa9aefd5faba63d55f1c3da7c4286", + "lib/guards.mjs": "b1b025a1fdcb63434733216a4faaa45112aa404f26f9d31df28921243d6a22f8", "lib/historical.mjs": "43b46395828daa3f19d8692e9bd22ea6cfc1d0dbac83814d727894f672a0ddd2", "lib/knip-reporter.mjs": "175d7d96e2913e8f6de04b6c706172258d9d7654a26ba7f9ed7edff79d3fc8ca", - "lib/reports.mjs": "e41c4994e444128ae0418eff7240f49de678a97112fa7d5171fbe680d26b6dff", + "lib/reports.mjs": "361e38ae981238db91f7b9a31af963ccbb89e3ae6745d1582b2086e6c64f0c9e", "lib/template.mjs": "c2a14b715f0c9dbe8b8243b142f4c6628967400fb8f92767537169e1b7bfd8f4", - "package-lock.json": "314de2cb801a00bb42240da3567274bac9725359c0429665c505653a51bf5f91", - "package.json": "e1228b0144b1d8bd79283bfacec0be9a54f636e973ba42de4b1cc3694972f57e", + "package-lock.json": "85c154a1d50a55d975a56f173a25eb5be3ef6ae7ffa111726531e31246621907", + "package.json": "188e7badefdaa40018946b7d320f8c87385ba146ead7e404b3abc5ec371eb249", "prettier.config.mjs": "7576563a9a30aff8eb3177f9cc2e4bc7a0f6b825042eadb9a619b86f519c5e5e", "run.mjs": "7cf26128cdf63f3a7ccab7341a0a274a352fd9bda0521ba41e37216bb9cc249f", "seal.mjs": "fb38c8e08ea3cf9f4dcd95e45ffc931aea3b7a54181fed88d7379f8aad4c050d", - "test/cli.test.mjs": "63350dcf9c58c0e41d0dcc49bb8b9eef8dd0c01e781f25a58490f1476f58e94a", + "test/cli.test.mjs": "bd3445143a45aabba2a95593aace73f96223389f7a440c2ef41bf639bb4ec12a", "test/complexity.test.mjs": "ba1f702ba94de3fcad93cb5e43a49bc2456be1426c81754149420d210f2b3952", + "test/guard-history.test.mjs": "b699cc394b9c843a59798f28fe2b5dd6c199c3f3e16dc1c61c35724a935fc23e", "test/ratchet.test.mjs": "324a932c5a52d1b037e111042e1b41e5686f8d4dbfd0307faf86cee649ebc27a", - "test/reports.test.mjs": "3d5be0312d4870dd00e4cdafaa422d1e0d4e0d3ecaf4dc65a73a4dac577bfbfa", + "test/reports.test.mjs": "521a13da2f16628b4a30de7040529e0b7b7e25e7db79d19de8004945d49c5e0c", "tsconfig.next.json": "d900a35fcb0852376b06854df5be2f8f178e26cb5383b93b07ebeb6220e30faf", "tsconfig.node.json": "627661abcb7e992c4bfaf98d6e68a12678573b5d4f6e821ea0fa43fa6e233f28", "tsconfig.svelte.json": "5777247ea98aaf2d886b87cd12206a641377edcdd086ce5fd5b58adc6f0bc771" diff --git a/tools/quality/test/cli.test.mjs b/tools/quality/test/cli.test.mjs index 33425a7..92c7b32 100644 --- a/tools/quality/test/cli.test.mjs +++ b/tools/quality/test/cli.test.mjs @@ -823,3 +823,83 @@ test('actual raw complexity CLI distinguishes lexical loops and rejects depth tr f.close(); } }); + +/** Raw historical replay resolves Core-shaped duplicate try blocks while rejecting swaps, added scopes and increased nesting. */ +test('actual complexity CLI validates structural context without transferring debt', () => { + const f = fixture(); + try { + f.write( + 'eslint.config.mjs', + "export default [{rules:{'max-depth':['error',3]}}];", + ); + const text = + 'if (match) { if (template.rejected) { try { send(template); } catch (error) { record(error); } } else { saveText(template); } }'; + const document = text.replace('saveText', 'saveDocument'); + /** Retain branch code while varying only a surrounding while depth or branch sequence. */ + const source = (loops, branches) => + `function collect(root) { ${'while (root.ready) {'.repeat(loops)} if (root.templates) { for (const template of root.templates) { ${branches.join('\n')} } } ${'}'.repeat(loops)} }`; + f.write('src/index.js', source(2, [text, document])); + const base = f.commit(); + const args = [ + 'report', + '--kind', + 'eslint', + '--base', + base, + '--bootstrap', + '--owner', + 'quality-fixture', + '--expires', + '2099-12-31', + '--reason', + 'Historical structural branch depth', + '--issue', + 'TEST-1', + '--', + process.execPath, + fileURLToPath( + new URL('../node_modules/eslint/bin/eslint.js', import.meta.url), + ), + '--config', + 'eslint.config.mjs', + '--no-inline-config', + '--format', + 'json', + 'src/index.js', + ]; + const original = f.run(...args); + assert.equal(original.status, 0, original.stderr); + const old = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.ok(old.entries.some(({ key }) => key.includes('context-group:'))); + f.write('src/index.js', source(1, [text, document])); + const reduced = f.run(...args); + assert.equal(reduced.status, 0, reduced.stderr); + const retained = JSON.parse( + readFileSync(join(f.cwd, 'quality/baselines/complexity.json'), 'utf8'), + ); + assert.ok( + retained.entries.every((entry) => + old.entries.some( + (prior) => prior.key === entry.key && prior.value >= entry.value, + ), + ), + ); + for (const current of [ + source(2, [document, text]), + source(2, [text, document, text.replace('saveText', 'saveImage')]), + source(3, [text, document]), + ]) { + f.write('src/index.js', current); + const violation = f.run(...args); + assert.equal(violation.status, 1, violation.stderr); + assert.match( + violation.stderr, + /Cannot baseline a new\/increased finding/, + ); + } + } finally { + f.close(); + } +}); diff --git a/tools/quality/test/guard-history.test.mjs b/tools/quality/test/guard-history.test.mjs new file mode 100644 index 0000000..b49ffa7 --- /dev/null +++ b/tools/quality/test/guard-history.test.mjs @@ -0,0 +1,113 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const runner = fileURLToPath(new URL('../run.mjs', import.meta.url)); + +/** Build independent Git history so the actual CLI must distinguish trusted historical syntax from current source failures. */ +function fixture() { + const cwd = mkdtempSync(join(tmpdir(), 'quality-guard-history-')); + const env = { ...process.env, CI: '', QUALITY_BASE: undefined }; + for (const name of Object.keys(env)) { + if (name.startsWith('GIT_')) delete env[name]; + } + env.GIT_CONFIG_GLOBAL = process.platform === 'win32' ? 'NUL' : '/dev/null'; + env.GIT_CONFIG_NOSYSTEM = '1'; + mkdirSync(join(cwd, 'src')); + writeFileSync( + join(cwd, 'quality.config.mjs'), + "export default {schemaVersion:1,profile:'B',defaultBranch:'main',sourceRoots:['src','quality.config.mjs']};\n", + ); + const git = (...args) => { + const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' }); + assert.equal(result.status, 0, result.stderr); + return result.stdout.trim(); + }; + git('init', '-b', 'main'); + git('config', 'user.name', 'Quality fixture'); + git('config', 'user.email', 'quality@example.invalid'); + return { + cwd, + write: (text) => writeFileSync(join(cwd, 'src/Widget.svelte'), text), + commit: () => { + git('add', '.'); + git('commit', '-m', 'test: historical syntax'); + return git('rev-parse', 'HEAD'); + }, + run: (base) => + spawnSync(process.execPath, [runner, 'guards', '--base', base], { + cwd, + env, + encoding: 'utf8', + timeout: 30_000, + }), + close: () => rmSync(cwd, { recursive: true, force: true }), + }; +} + +test('actual guards require all current docs after unparseable trusted Svelte history', () => { + const f = fixture(); + try { + // Historical markup embeds script text in a JavaScript template; the old script extractor cannot parse that extra unit. + f.write( + '\n', + ); + const base = f.commit(); + f.write( + '\n

Readable widget

', + ); + const undocumented = f.run(base); + assert.notEqual(undocumented.status, 0); + assert.match(undocumented.stderr, /document stable/); + assert.match(undocumented.stderr, /document changed/); + assert.doesNotMatch(undocumented.stderr, /Cannot parse/); + + f.write( + '\n

Readable widget

', + ); + const documented = f.run(base); + assert.equal(documented.status, 0, documented.stderr); + assert.match(documented.stdout, /guards: PASS/); + + f.write(''); + const invalidCurrent = f.run(base); + assert.notEqual(invalidCurrent.status, 0); + assert.match(invalidCurrent.stderr, /Cannot parse src\/Widget\.svelte/); + + const invalidBase = f.run('nonexistent-trusted-reference'); + assert.notEqual(invalidBase.status, 0); + assert.match(invalidBase.stderr, /git rev-parse failed/); + } finally { + f.close(); + } +}); + +test('historical fallback does not consume source I/O or configuration failures', () => { + const f = fixture(); + try { + f.write(''); + const base = f.commit(); + rmSync(join(f.cwd, 'src/Widget.svelte')); + mkdirSync(join(f.cwd, 'src/Widget.svelte')); + const invalidInput = f.run(base); + assert.notEqual(invalidInput.status, 0); + assert.match( + invalidInput.stderr, + /EISDIR|illegal operation on a directory/, + ); + + writeFileSync( + join(f.cwd, 'quality.config.mjs'), + 'export default {schemaVersion:1,profile:"unknown",sourceRoots:["src"]};', + ); + const invalidConfig = f.run(base); + assert.notEqual(invalidConfig.status, 0); + assert.match(invalidConfig.stderr, /concrete profile/); + } finally { + f.close(); + } +}); diff --git a/tools/quality/test/reports.test.mjs b/tools/quality/test/reports.test.mjs index 053a7df..339ee55 100644 --- a/tools/quality/test/reports.test.mjs +++ b/tools/quality/test/reports.test.mjs @@ -236,3 +236,109 @@ test('identical loop bindings do not disambiguate indistinguishable callbacks', const loop = `for (const item of root.items) { run(${callback(2)}); }`; await assert.rejects(() => findings(loop + loop), /Ambiguous lexical scope/); }); + +/** Equal try blocks in text/document template branches need their differing surrounding branch bodies as evidence. */ +test('identical scopes use the nearest distinguishing structural branch context', async () => { + const source = `function templates(root) { + while (root.ready) { if (root.templates) { for (const template of root.templates) { + if (match) { if (template.rejected) { try { send(template); } catch (error) { record(error); } } else { saveText(template); } } + if (match) { if (template.rejected) { try { send(template); } catch (error) { record(error); } } else { saveDocument(template); } } + } } } + }`; + const records = await findings(source, 'max-depth', 3); + assert.ok(records.some(({ key }) => key.includes('context-group:'))); + assert.equal(new Set(records.map(({ key }) => key)).size, records.length); + const callbacks = `if (ready) { run(${callback(2)}); } else { run(${callback(2)}); } + try { run(${callback(2)}); } catch (error) { run(${callback(2)}); } finally { run(${callback(2)}); } + switch (kind) { case 'first': run(${callback(2)}); break; case 'second': run(${callback(2)}); break; default: run(${callback(2)}); } + while (active) { run(${callback(2)}); } + do { run(${callback(2)}); } while (active);`; + const owned = await findings(callbacks); + assert.equal(owned.length, 10); + assert.equal(new Set(owned.map(({ key }) => key)).size, 10); + assert.deepEqual( + await findings( + callbacks.replaceAll('run(', '/* Branch documentation */ run('), + ), + owned, + ); +}); + +/** Identical branches and predicates provide no trustworthy address merely because they appear at different source offsets. */ +test('identical structural ancestry still fails closed', async () => { + const branch = `if (ready) { run(${callback(2)}); }`; + await assert.rejects( + () => findings(branch + branch), + /Ambiguous lexical scope/, + ); +}); + +/** ESLint names object-property callbacks at the property token, which precedes the actual function start. */ +test('object property diagnostics resolve only their direct callable initializer', async () => { + for (const fn of [ + `async ${callback(2)}`, + 'async function(value) { if (value.p0) return 1; if (value.p1) return 2; return 0; }', + ]) { + const source = `const endpoints = { handler: ${fn}, unrelated: 1 };`; + const report = await analyze(source); + assert.equal(report[0].messages[0].column, source.indexOf('handler') + 1); + const records = eslintRecords(report); + assert.equal(records.length, 1); + assert.match(records[0].key, /handler/); + assert.deepEqual( + await findings(source.replace('handler:', 'handler: /* Contract */')), + records, + ); + for (const column of [ + source.indexOf('handler:') + 'handler'.length + 1, + source.indexOf('unrelated') + 1, + ]) { + assert.throws( + () => + eslintRecords([ + { + filePath: 'invalid-location.js', + source, + messages: [ + { + ruleId: 'complexity', + line: 1, + column, + message: 'Function has a complexity of 4.', + }, + ], + }, + ]), + /No lexical scope/, + ); + } + } + assert.throws( + () => + eslintRecords([ + { + filePath: 'invalid-location.js', + source: 'const settings = { threshold: 5 };', + messages: [ + { + ruleId: 'complexity', + line: 1, + column: 20, + message: 'Function has a complexity of 4.', + }, + ], + }, + ]), + /No lexical scope/, + ); +}); + +/** An object callback diagnostic inside another function must not consume that enclosing function's complexity allowance. */ +test('object callbacks retain their own budget inside enclosing functions', async () => { + const source = `function useCheckout() { return useMutation({ onError: ${callback(2)} }); }`; + const report = await analyze(source); + assert.equal(report[0].messages.length, 1); + const record = eslintRecords(report)[0]; + assert.match(record.scope, /PropertyAssignment.*onError.*ArrowFunction/); + assert.equal(record.value, 3); +}); From fe223b90998d5fb4af4665041d13ca7e870b3d54 Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Tue, 13 Oct 2026 02:57:28 +0500 Subject: [PATCH 7/9] fix(quality): support older Git in shared fixtures --- tools/quality/README.md | 15 ++- tools/quality/package-lock.json | 4 +- tools/quality/package.json | 2 +- tools/quality/template-manifest.json | 13 +-- tools/quality/test/cli.test.mjs | 3 +- tools/quality/test/git-portability.test.mjs | 100 ++++++++++++++++++++ tools/quality/test/guard-history.test.mjs | 3 +- 7 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 tools/quality/test/git-portability.test.mjs diff --git a/tools/quality/README.md b/tools/quality/README.md index 2e4def1..9c78337 100644 --- a/tools/quality/README.md +++ b/tools/quality/README.md @@ -1,4 +1,4 @@ -# 1MSG quality template 1.0.10 +# 1MSG quality template 1.0.11 Vendored, private template for 1MSG-156. Install its exact tools with `npm --prefix tools/quality ci` on Node 24.19.0. The lockfile and @@ -137,3 +137,16 @@ removes all inherited function signatures: every current function in that file must have documentation. Current syntax errors and historical/current I/O, configuration or Git failures remain fatal. This conservative documentation fallback does not alter metric analysis, source coverage or debt baselines. + +## 1.0.11 migration + +Test repositories now initialize their default branch with `git init` followed by +`git symbolic-ref HEAD refs/heads/main`. This works on the older Git installation +used by the self-hosted runner, which does not support `git init -b`. A regression +fixture rejects that unsupported option and replays both actual Git-backed test +families through a subprocess-only Node preload shim; all other Git operations +remain real. The fixture uses no Unix executable or shebang assumptions and does +not modify the runtime Git driver or skip checks on Windows. +Only fixture setup changes: analysis rules, historical identities and baseline +records are unchanged. Copy the complete sealed version without changing debt +owners, reasons, issue links, expiry dates or baseline entries. diff --git a/tools/quality/package-lock.json b/tools/quality/package-lock.json index 5e6bab1..527d33a 100644 --- a/tools/quality/package-lock.json +++ b/tools/quality/package-lock.json @@ -1,12 +1,12 @@ { "name": "@1msg/quality-template", - "version": "1.0.10", + "version": "1.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@1msg/quality-template", - "version": "1.0.10", + "version": "1.0.11", "dependencies": { "@eslint/js": "9.39.5", "dependency-cruiser": "18.6.0", diff --git a/tools/quality/package.json b/tools/quality/package.json index 07e7cfe..67942c0 100644 --- a/tools/quality/package.json +++ b/tools/quality/package.json @@ -1,6 +1,6 @@ { "name": "@1msg/quality-template", - "version": "1.0.10", + "version": "1.0.11", "private": true, "type": "module", "engines": { diff --git a/tools/quality/template-manifest.json b/tools/quality/template-manifest.json index f2cf559..210f229 100644 --- a/tools/quality/template-manifest.json +++ b/tools/quality/template-manifest.json @@ -1,8 +1,8 @@ { "schemaVersion": 1, - "version": "1.0.10", + "version": "1.0.11", "files": { - "README.md": "50df5c60db609380718c9228de8f8f53cf1949673bd66f71a9f0192fae1d6dbd", + "README.md": "6760e252841ba82078ede1589a474dd19c8f77f828245e181f34e842465c3aa1", "canonical-duplicates.mjs": "a3583fdc3e344af448ea8d2543c2398daaa03fa10a0739da486afb04e35425ba", "complexity.mjs": "4ed5553c392621e0615a48c12e12300c8602ed2a28cfbaff67002acf82e49cac", "eslint.mjs": "607ddfaac3d365fedd7dbc5e21c781d448347b1c36662f3492e4364ff9f22ded", @@ -13,14 +13,15 @@ "lib/knip-reporter.mjs": "175d7d96e2913e8f6de04b6c706172258d9d7654a26ba7f9ed7edff79d3fc8ca", "lib/reports.mjs": "361e38ae981238db91f7b9a31af963ccbb89e3ae6745d1582b2086e6c64f0c9e", "lib/template.mjs": "c2a14b715f0c9dbe8b8243b142f4c6628967400fb8f92767537169e1b7bfd8f4", - "package-lock.json": "85c154a1d50a55d975a56f173a25eb5be3ef6ae7ffa111726531e31246621907", - "package.json": "188e7badefdaa40018946b7d320f8c87385ba146ead7e404b3abc5ec371eb249", + "package-lock.json": "9fc7d279d2bade1c42f807d99a107d77f15a8e42edd2a9b32b4b5ce1a4fda35f", + "package.json": "deb5fd5e972aec2926b21e31d02c47e9c35d507048e83ab0ed161425151546e2", "prettier.config.mjs": "7576563a9a30aff8eb3177f9cc2e4bc7a0f6b825042eadb9a619b86f519c5e5e", "run.mjs": "7cf26128cdf63f3a7ccab7341a0a274a352fd9bda0521ba41e37216bb9cc249f", "seal.mjs": "fb38c8e08ea3cf9f4dcd95e45ffc931aea3b7a54181fed88d7379f8aad4c050d", - "test/cli.test.mjs": "bd3445143a45aabba2a95593aace73f96223389f7a440c2ef41bf639bb4ec12a", + "test/cli.test.mjs": "49c55b1b4cf2ca2214b0f1b5d2bd973eac43005e14606f27a2a4aa682e0e691d", "test/complexity.test.mjs": "ba1f702ba94de3fcad93cb5e43a49bc2456be1426c81754149420d210f2b3952", - "test/guard-history.test.mjs": "b699cc394b9c843a59798f28fe2b5dd6c199c3f3e16dc1c61c35724a935fc23e", + "test/git-portability.test.mjs": "0e65759ff3a1aa87c20d20ae147154d980381c2e8075f57f341ef3c53c3b3f62", + "test/guard-history.test.mjs": "00b17403b3ef17cc5d83c681c990ca98b6a0ad4857a95d501d2cf8f390aa7b1e", "test/ratchet.test.mjs": "324a932c5a52d1b037e111042e1b41e5686f8d4dbfd0307faf86cee649ebc27a", "test/reports.test.mjs": "521a13da2f16628b4a30de7040529e0b7b7e25e7db79d19de8004945d49c5e0c", "tsconfig.next.json": "d900a35fcb0852376b06854df5be2f8f178e26cb5383b93b07ebeb6220e30faf", diff --git a/tools/quality/test/cli.test.mjs b/tools/quality/test/cli.test.mjs index 92c7b32..0c8d1cd 100644 --- a/tools/quality/test/cli.test.mjs +++ b/tools/quality/test/cli.test.mjs @@ -40,7 +40,8 @@ function fixture() { assert.equal(result.status, 0, result.stderr); return result.stdout.trim(); }; - git('init', '-b', 'main'); + git('init'); + git('symbolic-ref', 'HEAD', 'refs/heads/main'); git('config', 'user.email', 'quality@example.invalid'); git('config', 'user.name', 'Quality fixture'); const commit = () => { diff --git a/tools/quality/test/git-portability.test.mjs b/tools/quality/test/git-portability.test.mjs new file mode 100644 index 0000000..832c379 --- /dev/null +++ b/tools/quality/test/git-portability.test.mjs @@ -0,0 +1,100 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +/** Re-run both real Git fixture families under an old-Git command boundary; no successful Git operation is substituted. */ +test('Git fixtures retain real history when git init does not support branch flags', () => { + const cwd = mkdtempSync(join(tmpdir(), 'quality-old-git-')); + const log = join(cwd, 'git-calls.jsonl'); + const shim = join(cwd, 'reject-new-git-flags.mjs'); + // A Node preload avoids Unix executable/shebang assumptions and affects only this test's child processes. + writeFileSync( + shim, + ` +import childProcess from 'node:child_process'; +import { appendFileSync } from 'node:fs'; +import { syncBuiltinESMExports } from 'node:module'; +const actualSpawnSync = childProcess.spawnSync; +childProcess.spawnSync = function(command, args = [], options) { + if (command === 'git') { + appendFileSync(process.env.QUALITY_PORTABILITY_LOG, JSON.stringify(args) + '\\n'); + if (args[0] === 'init' && args.some((arg) => arg.startsWith('-b') || arg === '--initial-branch' || arg.startsWith('--initial-branch='))) { + return actualSpawnSync(process.execPath, ['-e', 'process.stderr.write("fixture: git init branch flags are unavailable\\\\n"); process.exit(97)'], options); + } + } + return actualSpawnSync(command, args, options); +}; +syncBuiltinESMExports(); +`, + ); + const env = { + ...process.env, + NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --import=${pathToFileURL(shim).href}`, + QUALITY_PORTABILITY_LOG: log, + }; + // This is an independent runner, not a child shard controlled by the outer Node test harness. + delete env.NODE_TEST_CONTEXT; + try { + // Prove the child-process boundary actually rejects the formerly required invocation. + const unsupported = spawnSync( + process.execPath, + [ + '--input-type=module', + '-e', + ` + import { spawnSync } from 'node:child_process'; + const result = spawnSync('git', ['init', '-b', 'main'], {encoding:'utf8'}); + process.stderr.write(result.stderr ?? ''); + process.exit(result.status ?? 1); + `, + ], + { cwd, env, encoding: 'utf8' }, + ); + assert.equal(unsupported.status, 97, unsupported.stderr); + assert.match(unsupported.stderr, /branch flags are unavailable/); + const template = fileURLToPath(new URL('..', import.meta.url)); + const result = spawnSync( + process.execPath, + [ + '--test', + '--test-reporter=tap', + '--test-name-pattern=real CLI blocks undocumented|actual guards require all current docs', + join(template, 'test/cli.test.mjs'), + join(template, 'test/guard-history.test.mjs'), + ], + { + cwd: template, + env, + encoding: 'utf8', + timeout: 60_000, + maxBuffer: 8 * 1024 * 1024, + }, + ); + assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); + assert.match(result.stdout, /# pass 2\b/); + assert.match(result.stdout, /# fail 0\b/); + const calls = readFileSync(log, 'utf8') + .trim() + .split('\n') + .map((line) => JSON.parse(line)); + assert.equal( + calls.filter((args) => JSON.stringify(args) === '["init"]').length, + 2, + ); + assert.equal( + calls.filter( + (args) => + JSON.stringify(args) === '["symbolic-ref","HEAD","refs/heads/main"]', + ).length, + 2, + ); + assert.ok(calls.some((args) => args[0] === 'commit')); + assert.ok(calls.some((args) => args[0] === 'show')); + } finally { + rmSync(cwd, { recursive: true, force: true }); + } +}); diff --git a/tools/quality/test/guard-history.test.mjs b/tools/quality/test/guard-history.test.mjs index b49ffa7..390086e 100644 --- a/tools/quality/test/guard-history.test.mjs +++ b/tools/quality/test/guard-history.test.mjs @@ -27,7 +27,8 @@ function fixture() { assert.equal(result.status, 0, result.stderr); return result.stdout.trim(); }; - git('init', '-b', 'main'); + git('init'); + git('symbolic-ref', 'HEAD', 'refs/heads/main'); git('config', 'user.name', 'Quality fixture'); git('config', 'user.email', 'quality@example.invalid'); return { From 5d73db01edf4de1415850034b833db6134a9f08f Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Tue, 13 Oct 2026 08:39:57 +0500 Subject: [PATCH 8/9] chore(quality): check authored extensions and reject unprogrammed TypeScript --- .github/workflows/quality.yml | 2 +- eslint.config.mjs | 4 + knip.json | 8 +- package.json | 17 ++- quality.config.mjs | 2 +- quality/hooks.test.mjs | 207 ++++++++++++++++++++++++++++++++++ quality/typescript-policy.mjs | 24 ++++ 7 files changed, 254 insertions(+), 10 deletions(-) create mode 100644 quality/hooks.test.mjs create mode 100644 quality/typescript-policy.mjs diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 7de2d74..2f56f21 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -24,7 +24,7 @@ jobs: java-version: "24" - run: npm ci - run: npm --prefix tools/quality ci - - run: node --test tools/quality/test/*.test.mjs + - run: npm run test:quality - run: node tools/quality/run.mjs guards - run: npm run format:check - run: npm run lint:static diff --git a/eslint.config.mjs b/eslint.config.mjs index b3efe8c..808bb4b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,9 @@ import { staticConfig, globals } from "./tools/quality/eslint.mjs"; export default [ + { + files: ["**/*.jsx"], + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, + }, { ignores: [ "**/node_modules/**", diff --git a/knip.json b/knip.json index 97f2c38..b889c5b 100644 --- a/knip.json +++ b/knip.json @@ -1,12 +1,14 @@ { "entry": [ "quality/generator/generate.mjs!", - "quality/generator/verify-negative.mjs!", + "quality/generator/verify-negative.mjs", "quality/postprocess.mjs!", "eslint.config.mjs", - "quality.config.mjs" + "quality.config.mjs", + "quality/hooks.test.mjs", + "quality/typescript-policy.mjs" ], - "project": ["quality/**/*.mjs!", "*.mjs"], + "project": ["quality/**/*.{js,jsx,mjs,cjs}!", "*.{js,jsx,mjs,cjs}"], "ignoreBinaries": ["java"], "ignore": ["tools/quality/**"] } diff --git a/package.json b/package.json index 4a1b8aa..f2a3672 100644 --- a/package.json +++ b/package.json @@ -8,18 +8,25 @@ }, "packageManager": "npm@11.17.0", "scripts": { - "format:check": "node tools/quality/node_modules/prettier/bin/prettier.cjs --check \"quality/**/*.mjs\" \"*.mjs\" package.json knip.json .github/workflows/quality.yml AGENTS.md", - "format": "node tools/quality/node_modules/prettier/bin/prettier.cjs --write \"quality/**/*.mjs\" \"*.mjs\" package.json knip.json .github/workflows/quality.yml AGENTS.md", - "lint:static": "node tools/quality/node_modules/eslint/bin/eslint.js . --max-warnings=0", + "format:check": "node tools/quality/node_modules/prettier/bin/prettier.cjs --check \"quality/**/*.{js,jsx,mjs,cjs}\" \"*.{js,jsx,mjs,cjs}\" package.json knip.json .github/workflows/quality.yml AGENTS.md", + "format": "node tools/quality/node_modules/prettier/bin/prettier.cjs --write \"quality/**/*.{js,jsx,mjs,cjs}\" \"*.{js,jsx,mjs,cjs}\" package.json knip.json .github/workflows/quality.yml AGENTS.md", + "lint:static": "node quality/typescript-policy.mjs && node tools/quality/node_modules/eslint/bin/eslint.js . --max-warnings=0", "quality:guards": "node tools/quality/run.mjs guards", "lint:unused:production": "node tools/quality/run.mjs unused-production", "lint:unused:full": "node tools/quality/run.mjs unused-full", "lint:duplicates": "node tools/quality/run.mjs duplicates", "lint:duplicates:full": "node tools/quality/run.mjs duplicates-full", - "test:quality": "node --test tools/quality/test/*.test.mjs", + "test:quality": "node --test tools/quality/test/*.test.mjs quality/hooks.test.mjs", "hooks:install": "node tools/quality/node_modules/husky/bin.js" }, "lint-staged": { - "*.{mjs,json,md,yml}": "node tools/quality/node_modules/prettier/bin/prettier.cjs --write" + "*.{js,jsx,mjs,cjs}": [ + "node tools/quality/node_modules/prettier/bin/prettier.cjs --write", + "node tools/quality/node_modules/eslint/bin/eslint.js --config eslint.config.mjs --max-warnings=0" + ], + "*.{ts,tsx,mts,cts}": [ + "node quality/typescript-policy.mjs" + ], + "*.{json,md,yml,yaml}": "node tools/quality/node_modules/prettier/bin/prettier.cjs --write" } } diff --git a/quality.config.mjs b/quality.config.mjs index ed8ca14..e12ec8a 100644 --- a/quality.config.mjs +++ b/quality.config.mjs @@ -5,7 +5,7 @@ export default { sourceRoots: ["quality", "eslint.config.mjs", "quality.config.mjs"], generated: [], comments: { - full: ["quality/**/*.mjs"], + full: ["quality/**/*.{js,jsx,mjs,cjs}"], }, duplicatePaths: ["quality"], duplicateMaxPercentage: 0, diff --git a/quality/hooks.test.mjs b/quality/hooks.test.mjs new file mode 100644 index 0000000..8ef4fea --- /dev/null +++ b/quality/hooks.test.mjs @@ -0,0 +1,207 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { + chmodSync, + statSync, + copyFileSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import test from "node:test"; + +const repository = resolve("."); +const configuration = { + source: "quality", + tooling: "quality", + typed: false, + files: [ + "package.json", + "quality/typescript-policy.mjs", + ".husky/pre-commit", + "eslint.config.mjs", + "eslint.static.config.mjs", + "eslint.typed.config.mjs", + "eslint.hook.config.mjs", + "tsconfig.json", + "tsconfig.quality.json", + "prettier.config.mjs", + ".prettierrc", + ".prettierrc.json", + ".prettierrc.js", + ".prettierignore", + ], + links: ["tools/quality"], +}; + +/** Run the real hook tools with an isolated Git identity and bounded child lifetime. */ +function command(root, program, args) { + const env = { + ...process.env, + HUSKY: "1", + GIT_CONFIG_NOSYSTEM: "1", + GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null", + }; + for (const key of Object.keys(env)) + if ( + key.startsWith("GIT_") && + !["GIT_CONFIG_NOSYSTEM", "GIT_CONFIG_GLOBAL"].includes(key) + ) + delete env[key]; + const result = spawnSync(program, args, { + cwd: root, + env, + encoding: "utf8", + timeout: 60_000, + maxBuffer: 8 * 1024 * 1024, + }); + assert.equal( + result.signal, + null, + `Fixture must finish by diagnostics: ${result.error ?? result.stderr}`, + ); + return result; +} + +/** Preserve full diagnostics when setup or a corrected commit unexpectedly fails. */ +function successful(root, program, args) { + const result = command(root, program, args); + assert.equal( + result.status, + 0, + `${program} ${args.join(" ")}\n${result.stdout}\n${result.stderr}`, + ); + return result.stdout.trim(); +} + +/** Copy production configuration and install the actual executable hook without borrowing the user's Git settings. */ +function fixture(t) { + const root = mkdtempSync(join(tmpdir(), "1msg-hook-extensions-")); + /** Remove only this disposable checkout after all real commit attempts finish. */ + t.after(() => rmSync(root, { recursive: true, force: true })); + for (const file of configuration.files) { + if (!existsSync(join(repository, file))) continue; + mkdirSync(dirname(join(root, file)), { recursive: true }); + copyFileSync(join(repository, file), join(root, file)); + } + for (const directory of configuration.links) { + mkdirSync(dirname(join(root, directory)), { recursive: true }); + symlinkSync(join(repository, directory), join(root, directory), "dir"); + } + const actualPackage = JSON.parse( + readFileSync(join(repository, "package.json"), "utf8"), + ); + writeFileSync(join(root, "package.json"), JSON.stringify(actualPackage)); + successful(root, "git", ["init", "--quiet"]); + successful(root, "git", ["config", "user.name", "Quality fixture"]); + successful(root, "git", [ + "config", + "user.email", + "quality-fixture@example.invalid", + ]); + successful(root, "git", [ + "-c", + "commit.gpgsign=false", + "commit", + "--quiet", + "--allow-empty", + "-m", + "fixture setup", + ]); + chmodSync(join(root, ".husky/pre-commit"), 0o755); + successful(root, process.execPath, [ + "tools/quality/node_modules/husky/bin.js", + ]); + assert.equal( + successful(root, "git", ["config", "--local", "core.hooksPath"]), + ".husky/_", + ); + assert.ok(statSync(join(root, ".husky/_/pre-commit")).mode & 0o111); + return root; +} + +/** Stage normalized fixture text so the intended lint rule, rather than unrelated formatting, refuses the commit. */ +function stage(root, files, source) { + for (const file of files) { + mkdirSync(dirname(join(root, file)), { recursive: true }); + writeFileSync(join(root, file), source); + } + successful(root, process.execPath, [ + "tools/quality/node_modules/prettier/bin/prettier.cjs", + "--write", + ...files, + ]); + successful(root, "git", ["add", "--", ...files]); +} + +/** A rejected commit must retain HEAD and identify every tested extension, preventing one covered file from masking an ignored peer. */ +function rejected(root, files, diagnostic) { + const head = successful(root, "git", ["rev-parse", "HEAD"]); + const result = command(root, "git", [ + "-c", + "commit.gpgsign=false", + "commit", + "--quiet", + "-m", + "reject invalid authored extensions", + ]); + const output = `${result.stdout}\n${result.stderr}`.replaceAll("\\", "/"); + assert.notEqual(result.status, 0, output); + assert.match(output, diagnostic); + for (const file of files) { + const sections = output + .split(/\n\s*\n/) + .filter((section) => section.includes(file)); + assert.ok( + sections.some((section) => diagnostic.test(section)), + `Missing intended diagnostic for ${file}:\n${output}`, + ); + } + assert.equal(successful(root, "git", ["rev-parse", "HEAD"]), head); + successful(root, "git", ["reset", "--quiet", "HEAD", "--", ...files]); + for (const file of files) rmSync(join(root, file)); +} + +/** Exercise actual commits for all authored extensions; strict TS boundaries additionally require their configured program. */ +test("installed pre-commit checks JavaScript and rejects unconfigured TypeScript", (t) => { + const root = fixture(t); + /** Keep fixture paths inside the actual source program and give each extension its own diagnostic. */ + const paths = (extensions) => + extensions.map( + (extension) => `${configuration.source}/hook-${extension}.${extension}`, + ); + const javascript = paths(["js", "jsx", "mjs", "cjs"]); + const typescript = paths(["ts", "tsx", "mts", "cts"]); + stage( + root, + javascript, + "const value = process.env.HOOK_FIXTURE_VALUE;\nconsole.log(value == 1);\n", + ); + rejected(root, javascript, /eqeqeq/); + stage( + root, + typescript, + "/** Preserve the supplied untrusted value. */\nexport function same(value: any) { return value; }\n", + ); + rejected(root, typescript, /TypeScript requires a configured strict program/); + + stage( + root, + javascript, + "const value = process.env.HOOK_FIXTURE_VALUE;\nconsole.log(value === 1);\n", + ); + successful(root, "git", [ + "-c", + "commit.gpgsign=false", + "commit", + "--quiet", + "-m", + "accept corrected authored extensions", + ]); +}); diff --git a/quality/typescript-policy.mjs b/quality/typescript-policy.mjs new file mode 100644 index 0000000..0abfaa1 --- /dev/null +++ b/quality/typescript-policy.mjs @@ -0,0 +1,24 @@ +import { execFileSync } from "node:child_process"; +import { relative, resolve } from "node:path"; + +// Native SDKs currently have no authored TypeScript. New TS must gain a strict program before it can enter the repository. +const requested = process.argv.slice(2); +const files = requested.length + ? requested + : execFileSync( + "git", + ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], + { encoding: "utf8" }, + ).split("\0"); +/** Normalize lint-staged absolute paths before excluding the sealed vendor; repository-authored TS remains forbidden until a program exists. */ +const unsupported = files.filter((file) => { + const path = relative(process.cwd(), resolve(file)).replaceAll("\\", "/"); + return ( + /\.(?:ts|tsx|mts|cts)$/.test(path) && !path.startsWith("tools/quality/") + ); +}); +for (const file of unsupported) + console.error( + `${file}: TypeScript requires a configured strict program; preserve the native SDK profile and explicitly configure authored TS tooling first.`, + ); +if (unsupported.length) process.exitCode = 1; From ff82c3c8784cbb05915e8fdb0a48fce39435362f Mon Sep 17 00:00:00 2001 From: Alibek Seksenali Date: Tue, 13 Oct 2026 12:09:20 +0500 Subject: [PATCH 9/9] fix(quality): parse suppression comments with template context --- package.json | 3 +- quality/hooks.test.mjs | 1 + tools/quality/README.md | 20 ++++ tools/quality/lib/guards.mjs | 47 +++++++-- tools/quality/package-lock.json | 4 +- tools/quality/package.json | 2 +- tools/quality/staged.mjs | 43 ++++++++ tools/quality/template-manifest.json | 14 +-- tools/quality/test/cli.test.mjs | 74 ++++++++++++++ .../quality/test/suppression-syntax.test.mjs | 97 +++++++++++++++++++ 10 files changed, 285 insertions(+), 20 deletions(-) create mode 100644 tools/quality/staged.mjs create mode 100644 tools/quality/test/suppression-syntax.test.mjs diff --git a/package.json b/package.json index f2a3672..bb752be 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,9 @@ }, "lint-staged": { "*.{js,jsx,mjs,cjs}": [ + "node tools/quality/staged.mjs --config eslint.config.mjs --", "node tools/quality/node_modules/prettier/bin/prettier.cjs --write", - "node tools/quality/node_modules/eslint/bin/eslint.js --config eslint.config.mjs --max-warnings=0" + "node tools/quality/node_modules/eslint/bin/eslint.js --config eslint.config.mjs --max-warnings=0 --no-warn-ignored" ], "*.{ts,tsx,mts,cts}": [ "node quality/typescript-policy.mjs" diff --git a/quality/hooks.test.mjs b/quality/hooks.test.mjs index 8ef4fea..416388e 100644 --- a/quality/hooks.test.mjs +++ b/quality/hooks.test.mjs @@ -23,6 +23,7 @@ const configuration = { typed: false, files: [ "package.json", + "quality.config.mjs", "quality/typescript-policy.mjs", ".husky/pre-commit", "eslint.config.mjs", diff --git a/tools/quality/README.md b/tools/quality/README.md index 9c78337..750bd5c 100644 --- a/tools/quality/README.md +++ b/tools/quality/README.md @@ -150,3 +150,23 @@ not modify the runtime Git driver or skip checks on Windows. Only fixture setup changes: analysis rules, historical identities and baseline records are unchanged. Copy the complete sealed version without changing debt owners, reasons, issue links, expiry dates or baseline entries. + +## 1.0.12 migration + +Suppression detection now scans trivia between parsed TypeScript tokens. A plain +scanner loses context after a template interpolation: it can mistake generated +directive text for a source comment and overlook a real directive after the +template. Parsed template segments, regex bodies and JSX text remain data; +comments inside interpolations/JSX expressions and at EOF remain enforceable. +Positive and negative syntax fixtures and the real guard CLI cover both failure +modes, including scoped exception requirements and Svelte script blocks. +Copy the complete sealed version. Metric identities, source coverage and debt +allowances do not change; rerun guards to detect any previously missed comments. + +The staged-file coverage preflight (`staged.mjs --config -- +`) permits ignored files only when the repository inventory explicitly +declares them or they belong to the verified sealed preset. Run it before ESLint +with `--no-warn-ignored`: this supports preset updates and generated files while +still rejecting an accidentally ignored authored file. An added or altered +unsealed vendor file fails integrity validation; an arbitrary ESLint ignore alone +never grants an exemption. diff --git a/tools/quality/lib/guards.mjs b/tools/quality/lib/guards.mjs index 69e3bde..0e3fddb 100644 --- a/tools/quality/lib/guards.mjs +++ b/tools/quality/lib/guards.mjs @@ -280,29 +280,56 @@ function historicalFunctions(path, text) { } } -/** Identify suppression directives in comments only, never in strings or generated documentation examples. */ -export function suppressionErrors(path, text, config) { - const errors = []; - for (const { ast } of parseUnits(path, text)) { +/** Yield parsed lexical leaves without descending into documentation or splitting literal/JSX content. */ +function* syntaxTokens(node, ast) { + if ( + (node.kind >= ts.SyntaxKind.FirstJSDocNode && + node.kind <= ts.SyntaxKind.LastJSDocNode) || + node.kind === ts.SyntaxKind.JSDocComment + ) + return; + const children = node.getChildren(ast); + if (children.length) { + for (const child of children) yield* syntaxTokens(child, ast); + } else if (node.kind !== ts.SyntaxKind.SyntaxList) { + yield node; + } +} + +/** Scan only trivia between parsed tokens; interpolation expressions retain comments while template/regex/JSX bodies stay opaque. */ +function* sourceComments(ast) { + let end = 0; + for (const token of syntaxTokens(ast, ast)) { + const start = token.getStart(ast); const scanner = ts.createScanner( ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, - ast.text, + ast.text.slice(end, start), ); for ( - let token = scanner.scan(); - token !== ts.SyntaxKind.EndOfFileToken; - token = scanner.scan() + let trivia = scanner.scan(); + trivia !== ts.SyntaxKind.EndOfFileToken; + trivia = scanner.scan() ) { if ( ![ ts.SyntaxKind.SingleLineCommentTrivia, ts.SyntaxKind.MultiLineCommentTrivia, - ].includes(token) + ].includes(trivia) ) continue; - const comment = scanner.getTokenText(); + yield scanner.getTokenText(); + } + end = token.end; + } +} + +/** Identify suppression directives in comments only, never in strings or generated documentation examples. */ +export function suppressionErrors(path, text, config) { + const errors = []; + for (const { ast } of parseUnits(path, text)) { + for (const comment of sourceComments(ast)) { if ( /@ts-(?:ignore|nocheck)\b/.test(comment) || /eslint-disable(?!-next-line|-line)\b/.test(comment) diff --git a/tools/quality/package-lock.json b/tools/quality/package-lock.json index 527d33a..bf75285 100644 --- a/tools/quality/package-lock.json +++ b/tools/quality/package-lock.json @@ -1,12 +1,12 @@ { "name": "@1msg/quality-template", - "version": "1.0.11", + "version": "1.0.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@1msg/quality-template", - "version": "1.0.11", + "version": "1.0.12", "dependencies": { "@eslint/js": "9.39.5", "dependency-cruiser": "18.6.0", diff --git a/tools/quality/package.json b/tools/quality/package.json index 67942c0..f6bbd11 100644 --- a/tools/quality/package.json +++ b/tools/quality/package.json @@ -1,6 +1,6 @@ { "name": "@1msg/quality-template", - "version": "1.0.11", + "version": "1.0.12", "private": true, "type": "module", "engines": { diff --git a/tools/quality/staged.mjs b/tools/quality/staged.mjs new file mode 100644 index 0000000..bbfa798 --- /dev/null +++ b/tools/quality/staged.mjs @@ -0,0 +1,43 @@ +import { ESLint } from 'eslint'; +import { readFileSync, existsSync } from 'node:fs'; +import { relative, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { matches } from './lib/guards.mjs'; +import { verifyTemplate } from './lib/template.mjs'; + +const [, , flag, configuration, separator, ...files] = process.argv; +if (flag !== '--config' || !configuration || separator !== '--') + throw new Error('Usage: staged.mjs --config -- '); + +const { default: inventory } = await import( + pathToFileURL(resolve('quality.config.mjs')).href +); +const approved = [ + ...(inventory.generated ?? []), + ...(inventory.excluded ?? []), +].map((entry) => entry.pattern); +const manifestPath = 'tools/quality/template-manifest.json'; +let vendorFiles = new Set(); +if (existsSync(manifestPath)) { + verifyTemplate('HEAD'); + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); + vendorFiles = new Set([ + manifestPath, + ...Object.keys(manifest.files).map((path) => `tools/quality/${path}`), + ]); +} +const eslint = new ESLint({ overrideConfigFile: configuration }); +const uncovered = []; +for (const file of files) { + const path = relative(process.cwd(), resolve(file)).replaceAll('\\', '/'); + if ( + (await eslint.isPathIgnored(file)) && + !vendorFiles.has(path) && + !matches(path, approved) + ) + uncovered.push(path); +} +if (uncovered.length) + throw new Error( + `Staged authored files have no ESLint coverage:\n${uncovered.join('\n')}`, + ); diff --git a/tools/quality/template-manifest.json b/tools/quality/template-manifest.json index 210f229..4e79096 100644 --- a/tools/quality/template-manifest.json +++ b/tools/quality/template-manifest.json @@ -1,29 +1,31 @@ { "schemaVersion": 1, - "version": "1.0.11", + "version": "1.0.12", "files": { - "README.md": "6760e252841ba82078ede1589a474dd19c8f77f828245e181f34e842465c3aa1", + "README.md": "80380757947a33100e4f43f7808b129f2c3cdc81c380614fc2e55153ef5f124a", "canonical-duplicates.mjs": "a3583fdc3e344af448ea8d2543c2398daaa03fa10a0739da486afb04e35425ba", "complexity.mjs": "4ed5553c392621e0615a48c12e12300c8602ed2a28cfbaff67002acf82e49cac", "eslint.mjs": "607ddfaac3d365fedd7dbc5e21c781d448347b1c36662f3492e4364ff9f22ded", "lib/baseline.mjs": "7876ba90b80448d9c9003c21e348095ed22630c41fc26ed944984742020549dd", "lib/git.mjs": "b7c26871526d864bf8d12f01c613d3bfa0adb2da9d072544d8f09407ef598758", - "lib/guards.mjs": "b1b025a1fdcb63434733216a4faaa45112aa404f26f9d31df28921243d6a22f8", + "lib/guards.mjs": "fe95129ef78aa5354446d8b37769ac27deb95d2b8bc978c5d3bafad538bd7836", "lib/historical.mjs": "43b46395828daa3f19d8692e9bd22ea6cfc1d0dbac83814d727894f672a0ddd2", "lib/knip-reporter.mjs": "175d7d96e2913e8f6de04b6c706172258d9d7654a26ba7f9ed7edff79d3fc8ca", "lib/reports.mjs": "361e38ae981238db91f7b9a31af963ccbb89e3ae6745d1582b2086e6c64f0c9e", "lib/template.mjs": "c2a14b715f0c9dbe8b8243b142f4c6628967400fb8f92767537169e1b7bfd8f4", - "package-lock.json": "9fc7d279d2bade1c42f807d99a107d77f15a8e42edd2a9b32b4b5ce1a4fda35f", - "package.json": "deb5fd5e972aec2926b21e31d02c47e9c35d507048e83ab0ed161425151546e2", + "package-lock.json": "238e6a8ff9303c66968b8e83ef028dfa49b7bb6a5ca485c0a7e65520d4c54365", + "package.json": "91adacf496715638a7e765035ca16d27e7a3c40ad3abbdd4bb4fc8c9574113eb", "prettier.config.mjs": "7576563a9a30aff8eb3177f9cc2e4bc7a0f6b825042eadb9a619b86f519c5e5e", "run.mjs": "7cf26128cdf63f3a7ccab7341a0a274a352fd9bda0521ba41e37216bb9cc249f", "seal.mjs": "fb38c8e08ea3cf9f4dcd95e45ffc931aea3b7a54181fed88d7379f8aad4c050d", - "test/cli.test.mjs": "49c55b1b4cf2ca2214b0f1b5d2bd973eac43005e14606f27a2a4aa682e0e691d", + "staged.mjs": "17db5c61a1369e6e740fd233887eca0033b64f2114dd3871b96108afb6123d49", + "test/cli.test.mjs": "a8e066e20790fc0d915b6a6d7ba11ca8d2af2a59ca6d51f4da232e7efb511f39", "test/complexity.test.mjs": "ba1f702ba94de3fcad93cb5e43a49bc2456be1426c81754149420d210f2b3952", "test/git-portability.test.mjs": "0e65759ff3a1aa87c20d20ae147154d980381c2e8075f57f341ef3c53c3b3f62", "test/guard-history.test.mjs": "00b17403b3ef17cc5d83c681c990ca98b6a0ad4857a95d501d2cf8f390aa7b1e", "test/ratchet.test.mjs": "324a932c5a52d1b037e111042e1b41e5686f8d4dbfd0307faf86cee649ebc27a", "test/reports.test.mjs": "521a13da2f16628b4a30de7040529e0b7b7e25e7db79d19de8004945d49c5e0c", + "test/suppression-syntax.test.mjs": "1b3dfc3a66ee9978491402e3683511c2d3a6d42203b283cd4a74578551a197b0", "tsconfig.next.json": "d900a35fcb0852376b06854df5be2f8f178e26cb5383b93b07ebeb6220e30faf", "tsconfig.node.json": "627661abcb7e992c4bfaf98d6e68a12678573b5d4f6e821ea0fa43fa6e233f28", "tsconfig.svelte.json": "5777247ea98aaf2d886b87cd12206a641377edcdd086ce5fd5b58adc6f0bc771" diff --git a/tools/quality/test/cli.test.mjs b/tools/quality/test/cli.test.mjs index 0c8d1cd..88b6aa6 100644 --- a/tools/quality/test/cli.test.mjs +++ b/tools/quality/test/cli.test.mjs @@ -99,6 +99,80 @@ test('real CLI blocks undocumented new functions, broad suppression and uncovere } }); +/** The real guard CLI distinguishes generated directive text from actual suppressions in and after interpolations. */ +test('actual guards CLI enforces comments around interpolated template data', () => { + const f = fixture(); + try { + f.write('src/index.js', 'console.log("app");\n'); + const base = f.commit(); + f.write( + 'src/index.js', + 'const generated = `a ${name} /* eslint-disable */`;\nconsole.log(generated);\n', + ); + const valid = f.run('guards', '--base', base); + assert.equal(valid.status, 0, valid.stderr); + for (const source of [ + 'const generated = `a ${ /* eslint-disable */ name } z`;\n', + 'const generated = `a ${name} z`; // @ts-nocheck\n', + ]) { + f.write('src/index.js', source); + const invalid = f.run('guards', '--base', base); + assert.equal(invalid.status, 1, invalid.stderr); + assert.match(invalid.stderr, /broad lint\/type suppression/); + } + } finally { + f.close(); + } +}); + +/** The actual staged preflight permits declared generated sources while rejecting ordinary ESLint ignores. */ +test('staged coverage rejects unowned ignores and validates sealed vendor files', () => { + const f = fixture(); + try { + f.write('src/index.js', 'console.log("app");\n'); + f.commit(); + f.write( + 'eslint.config.mjs', + 'export default [{ignores:["src/ignored.js","src/generated.js","tools/quality/**"]}];\n', + ); + f.write( + 'quality.config.mjs', + 'export default {generated:[{pattern:"src/generated.js",producer:"fixture"}]};\n', + ); + f.write('src/ignored.js', 'export const forgotten = 1;\n'); + f.write('src/generated.js', 'export const generated = 1;\n'); + const staged = fileURLToPath(new URL('../staged.mjs', import.meta.url)); + /** Invoke the installed ESLint resolver and real sealed inventory with only disposable files. */ + const check = (path) => + spawnSync( + process.execPath, + [staged, '--config', 'eslint.config.mjs', '--', path], + { cwd: f.cwd, encoding: 'utf8' }, + ); + assert.equal(check('src/index.js').status, 0); + assert.equal(check('src/generated.js').status, 0); + const ignored = check('src/ignored.js'); + assert.equal(ignored.status, 1); + assert.match( + ignored.stderr, + /Staged authored files have no ESLint coverage/, + ); + const template = fileURLToPath(new URL('..', import.meta.url)); + for (const path of [...templateFiles(template), 'template-manifest.json']) { + const destination = join(f.cwd, 'tools/quality', path); + mkdirSync(dirname(destination), { recursive: true }); + cpSync(join(template, path), destination); + } + assert.equal(check('tools/quality/lib/guards.mjs').status, 0); + f.write('tools/quality/rogue.js', 'export const bypass = 1;\n'); + const rogue = check('tools/quality/rogue.js'); + assert.equal(rogue.status, 1); + assert.match(rogue.stderr, /undeclared\/missing files/); + } finally { + f.close(); + } +}); + test('actual Knip CLI blocks an unused file and unresolved import', () => { const f = fixture(); try { diff --git a/tools/quality/test/suppression-syntax.test.mjs b/tools/quality/test/suppression-syntax.test.mjs new file mode 100644 index 0000000..4d5b0bf --- /dev/null +++ b/tools/quality/test/suppression-syntax.test.mjs @@ -0,0 +1,97 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { suppressionErrors } from '../lib/guards.mjs'; + +/** Template segments, regex bodies and JSX text are data even when they contain directive spelling. */ +test('suppression scanning preserves parsed literal boundaries', () => { + const sources = [ + [ + 'a.js', + 'const generated = `start ${name}\n/* eslint-disable */\n// @ts-nocheck\n`;', + ], + [ + 'a.ts', + 'const generated = `a ${`b ${name}\n// @ts-ignore\n`} c ${name} /* eslint-disable */`;', + ], + [ + 'a.mjs', + 'const generated = tag`head ${name}\n// @ts-nocheck\n${other} /* eslint-disable */`;', + ], + [ + 'a.ts', + String.raw`const pattern = /[/*]eslint-disable/; const other = /[//]@ts-ignore/;`, + ], + [ + 'a.tsx', + 'const view =
// @ts-ignore {name}/* eslint-disable */
;', + ], + [ + 'a.jsx', + 'const view =
{/* ordinary comment */} // @ts-ignore
;', + ], + [ + 'a.svelte', + '', + ], + ]; + for (const [path, source] of sources) { + assert.deepEqual( + suppressionErrors(path, source, {}), + [], + `${path}: ${source}`, + ); + } +}); + +/** Actual comments remain enforceable before, within and after expressions, including EOF trivia. */ +test('suppression scanning rejects comments inside template and JSX expressions', () => { + const sources = [ + ['a.js', 'const generated = `a ${ /* eslint-disable */ name } z`;'], + [ + 'a.ts', + 'const generated = `a ${`b ${ /* eslint-disable */ name } c`} d`;', + ], + ['a.mjs', 'const generated = `a ${name // @ts-ignore\n} z`;'], + ['a.cts', 'const generated = `a ${name} z`; // @ts-nocheck'], + ['a.tsx', 'const view =
{/* eslint-disable */ name}
;'], + ['a.jsx', 'const view =
;\n// @ts-ignore'], + ['a.ts', '/* eslint-disable */'], + [ + 'a.ts', + '/** @ts-ignore This comment is real documentation. */\nconst value = 1;', + ], + [ + 'a.ts', + String.raw`const pattern = /[/*]/; /* eslint-disable */ const value = 1;`, + ], + [ + 'a.svelte', + '', + ], + ]; + for (const [path, source] of sources) { + assert.equal( + suppressionErrors(path, source, {}).length, + 1, + `${path}: ${source}`, + ); + } +}); + +/** Malformed scoped comments must not gain an exemption merely by appearing in an interpolation. */ +test('suppression scanning retains scoped exception requirements', () => { + const source = 'const generated = `a ${ // eslint-disable-next-line\nname }`'; + assert.match(suppressionErrors('a.ts', source, {})[0], /must name rules/); + const negative = + 'const generated = `a ${ // @ts-expect-error: Verify an invalid primitive contract\nname }`'; + assert.match( + suppressionErrors('a.ts', negative, {})[0], + /negative type-test scope/, + ); + assert.deepEqual( + suppressionErrors('negative.test.ts', negative, { + negativeTypeTests: ['*.test.ts'], + }), + [], + ); +});