diff --git a/.do/app-spec-dev.yaml b/.do/app-spec-dev.yaml index 53402f9..ff067ff 100644 --- a/.do/app-spec-dev.yaml +++ b/.do/app-spec-dev.yaml @@ -47,6 +47,13 @@ services: http_port: 3456 health_check: http_path: /health + # Bare /health used DO defaults (1s timeout, 0s initial delay), which killed + # the instance during cold start (exit 128) and failed the whole deploy. + initial_delay_seconds: 30 + period_seconds: 10 + timeout_seconds: 5 + success_threshold: 1 + failure_threshold: 9 instance_size_slug: apps-s-1vcpu-0.5gb instance_count: 1 envs: @@ -58,3 +65,7 @@ services: scope: RUN_TIME type: SECRET value: "" + - key: SANDBOX_KEYCLOAK_CLIENT_SECRET + scope: RUN_TIME + type: SECRET + value: "" diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 182d370..2014582 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -41,14 +41,44 @@ jobs: runs-on: ubuntu-latest steps: - name: Trigger DigitalOcean App Platform Deployment + id: deploy run: | echo "Deploying to staging environment..." - curl -X POST \ + resp=$(curl -sS -X POST \ -H "Authorization: Bearer ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}" \ -H "Content-Type: application/json" \ "https://api.digitalocean.com/v2/apps/${{ secrets.DO_APP_ID_DEV }}/deployments" \ - --fail-with-body + --fail-with-body) + id=$(echo "$resp" | jq -r '.deployment.id') + echo "Triggered deployment $id" + echo "deployment_id=$id" >> "$GITHUB_OUTPUT" - - name: Deployment Triggered - run: echo "Deployment triggered successfully" + # Wait for the deployment to go live, then refresh the MCP index so search + # reflects the just-published docs (the mcp loads the index from the public + # URL at startup, which during a deploy is still the previous build). + # Best-effort: this step must never fail the workflow. + - name: Wait for deploy, then refresh MCP index + run: | + app="${{ secrets.DO_APP_ID_DEV }}" + id="${{ steps.deploy.outputs.deployment_id }}" + if [ -z "$id" ] || [ "$id" = "null" ]; then + echo "No deployment id — skipping refresh."; exit 0 + fi + for i in $(seq 1 60); do + phase=$(curl -sS \ + -H "Authorization: Bearer ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}" \ + "https://api.digitalocean.com/v2/apps/$app/deployments/$id" \ + | jq -r '.deployment.phase') + echo "deployment $id phase=$phase" + case "$phase" in + ACTIVE) + echo "Deployment live — refreshing MCP index..." + curl -sS -X POST https://docs.ottu.dev/mcp/refresh && echo " MCP index refreshed" + exit 0 ;; + ERROR|CANCELED|SUPERSEDED) + echo "Deployment ended as $phase — skipping refresh."; exit 0 ;; + esac + sleep 15 + done + echo "Timed out waiting for ACTIVE — skipping refresh."; exit 0 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 47f1967..c98f070 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,14 +41,44 @@ jobs: runs-on: ubuntu-latest steps: - name: Trigger DigitalOcean App Platform Deployment + id: deploy run: | echo "Deploying to production environment..." - curl -X POST \ + resp=$(curl -sS -X POST \ -H "Authorization: Bearer ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}" \ -H "Content-Type: application/json" \ "https://api.digitalocean.com/v2/apps/${{ secrets.DO_APP_ID_PROD }}/deployments" \ - --fail-with-body + --fail-with-body) + id=$(echo "$resp" | jq -r '.deployment.id') + echo "Triggered deployment $id" + echo "deployment_id=$id" >> "$GITHUB_OUTPUT" - - name: Deployment Triggered - run: echo "Deployment triggered successfully" + # Wait for the deployment to go live, then refresh the MCP index so search + # reflects the just-published docs (the mcp loads the index from the public + # URL at startup, which during a deploy is still the previous build). + # Best-effort: this step must never fail the workflow. + - name: Wait for deploy, then refresh MCP index + run: | + app="${{ secrets.DO_APP_ID_PROD }}" + id="${{ steps.deploy.outputs.deployment_id }}" + if [ -z "$id" ] || [ "$id" = "null" ]; then + echo "No deployment id — skipping refresh."; exit 0 + fi + for i in $(seq 1 60); do + phase=$(curl -sS \ + -H "Authorization: Bearer ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}" \ + "https://api.digitalocean.com/v2/apps/$app/deployments/$id" \ + | jq -r '.deployment.phase') + echo "deployment $id phase=$phase" + case "$phase" in + ACTIVE) + echo "Deployment live — refreshing MCP index..." + curl -sS -X POST https://docs.ottu.com/mcp/refresh && echo " MCP index refreshed" + exit 0 ;; + ERROR|CANCELED|SUPERSEDED) + echo "Deployment ended as $phase — skipping refresh."; exit 0 ;; + esac + sleep 15 + done + echo "Timed out waiting for ACTIVE — skipping refresh."; exit 0 diff --git a/docs/business/wallet/index.md b/docs/business/wallet/index.md index e02cddf..a10dec9 100644 --- a/docs/business/wallet/index.md +++ b/docs/business/wallet/index.md @@ -1,6 +1,6 @@ --- -title: Wallet -sidebar_label: Wallet +title: M-Wallet +sidebar_label: M-Wallet toc_min_heading_level: 2 toc_max_heading_level: 3 --- @@ -9,11 +9,11 @@ import StepGuide from "@site/src/components/StepGuide"; import { businessWalletScenariosSteps } from "@site/src/data/wallet-scenarios"; import FAQ, { FAQItem } from "@site/src/components/FAQ"; -# Wallet +# M-Wallet -Wallet is a stored balance you can grant to a customer in any currency you accept. Once a customer has wallet credit, they can spend it at checkout — fully or partially — on any future order with your business. +M-Wallet (merchant wallet) is a stored balance you can grant to a customer in any currency you accept. Once a customer has wallet credit, they can spend it at checkout — fully or partially — on any future order with your business. -## Why use Wallet +## Why use M-Wallet {#why-use-wallet} - **Refund without returning funds to the original card.** Useful when cards expire, or when you want to issue store credit instead of cash back. - **Issue goodwill credit or promotional balances** without payment-gateway fees. @@ -26,7 +26,7 @@ Wallet is a stored balance you can grant to a customer in any currency you accep 3. **Customer returns** for a future order with your business. 4. **They pay with wallet** — fully if the balance covers it, partially with another method otherwise. -## Refund to Wallet +## Refund to M-Wallet {#refund-to-wallet} Refunding to wallet credits the customer's wallet balance instead of returning funds through the payment gateway. The customer can spend the credit on their next order. @@ -69,9 +69,9 @@ Refunding to wallet credits the customer's wallet balance instead of returning f }, ]} /> -The next time the customer reaches checkout in your store (same currency), they'll see **Wallet (X.XXX USD)** as a payment method. See [Wallet at Checkout](#wallet-at-checkout) below for the customer-side flow. +The next time the customer reaches checkout in your store (same currency), they'll see **Wallet (X.XXX USD)** as a payment method. See [M-Wallet at Checkout](#wallet-at-checkout) below for the customer-side flow. -## Wallet at Checkout +## M-Wallet at Checkout {#wallet-at-checkout} This section explains what your customer sees when they have a wallet balance and reach checkout — so you can answer support questions and design messaging on your own site. @@ -99,7 +99,7 @@ If a checkout offers several payment gateways, make sure at most **one wallet pe - Display the customer's wallet balance on your own site or app — call the [Wallet Accounts API](/developers/payments/wallet/#api-reference). - Refund any future order to the same wallet to top it up. -- View full credit and debit history per customer in [Wallet Reporting](#wallet-reporting) below. +- View full credit and debit history per customer in [M-Wallet Reporting](#wallet-reporting) below. ## How fees are charged @@ -127,7 +127,7 @@ When the customer's wallet balance is **equal to or greater than the order amoun In the partial-wallet case above, the customer pays 30.000 at checkout, but only 20.000 of that is principal — the other 10.000 is the fee. The wallet still contributed its full 30.000 toward principal. The fee did not reduce the wallet's contribution; it was added on top of the PG leg. ::: -## Wallet Reporting +## M-Wallet Reporting {#wallet-reporting} The dashboard provides three screens for tracking wallet activity: **Accounts**, **Ledger**, and **Operations**. @@ -196,7 +196,7 @@ Lists individual wallet operations (a single credit, debit, or reservation cycle ## Reconciliation -Wallet-enabled payments settle across two separate rails — the **wallet rail** (funds moved internally between Ottu wallet balances, no card scheme involved) and the **PG rail** (funds charged through a payment gateway and settled by the acquirer). A single customer order may use one rail or both, so your month-end reconciliation needs to account for both sources of funds. +Wallet-enabled payments settle across two separate rails — the **wallet rail** (funds moved internally between wallet balances, no card scheme involved) and the **PG rail** (funds charged through a payment gateway and settled by the acquirer). A single customer order may use one rail or both, so your month-end reconciliation needs to account for both sources of funds. This section explains how Ottu represents a wallet-touched payment in your transaction records, and gives a step-by-step procedure for matching it against your wallet ledger and PG settlement files. @@ -287,7 +287,7 @@ Each parent transaction's webhook payload (and dashboard detail page) carries tw The key is **not present at all** on payments that never touched a wallet. Your reconciliation script should treat a missing key the same as an empty list. ::: -**Multi-wallet stacking.** If your setup supports more than one wallet provider (for example, Ottu Wallet + Qitaf), `customer_wallet_transactions` will contain one entry per provider that contributed funds. Sum the `amount` fields to get the total wallet portion of the payment. Ordering is not guaranteed — treat the array as a set. +**Multi-wallet stacking.** If your setup supports more than one wallet provider (for example, M-Wallet + Qitaf), `customer_wallet_transactions` will contain one entry per provider that contributed funds. Sum the `amount` fields to get the total wallet portion of the payment. Ordering is not guaranteed — treat the array as a set. **Cross-currency reservations.** When the wallet's native ledger currency differs from the order currency (rare, but possible), the entry includes `wallet_amount_native` and `wallet_currency` fields alongside `amount` and `currency`. Reconcile against the wallet ledger using the native values; use `amount`/`currency` for the order-side math. @@ -418,6 +418,6 @@ Export Accounts or Ledger data as **CSV** or **XLSX** for offline analysis and a ## What's Next? -- [Wallet for developers](/developers/payments/wallet/) — API and SDK integration. +- [M-Wallet for developers](/developers/payments/wallet/) — API and SDK integration. - [Payment Management](/business/payment-management/) — viewing and managing all transactions. -- [Refund to Wallet (developer reference)](/developers/operations#refund-to-wallet) — the API behind the dashboard refund flow. +- [Refund to M-Wallet (developer reference)](/developers/operations#refund-to-wallet) — the API behind the dashboard refund flow. diff --git a/docs/developers/operations.md b/docs/developers/operations.md index c86712e..bbbe0d8 100644 --- a/docs/developers/operations.md +++ b/docs/developers/operations.md @@ -119,9 +119,9 @@ Settles authorized funds. Only applicable to `authorized` transactions. Supports Returns funds to the customer. Applicable to `paid` or captured transactions. For authorized transactions, a capture must be completed first. Supports full or partial refund. Ottu offers an [approval feature](/business/operations/refund-void-access-control) for refunds, enabling a checker role to approve or reject requests. -##### Refund to Wallet {#refund-to-wallet} +##### Refund to M-Wallet {#refund-to-wallet} -Instead of returning funds through the original payment gateway, you can refund a payment directly to the customer's **wallet** balance. The customer can then spend that credit at any future Ottu checkout in the same currency. See [Wallet](/developers/payments/wallet/) for the full feature overview. +Instead of returning funds through the original payment gateway, you can refund a payment directly to the customer's **wallet** balance. The customer can then spend that credit at any future Ottu checkout in the same currency. See [M-Wallet](/developers/payments/wallet/) for the full feature overview. **When to use:** @@ -152,7 +152,7 @@ Wallet credits are immutable — a duplicate refund-to-wallet call creates a sec | 409 | `idempotency_conflict` | Same Idempotency-Key reused with different payload | | 422 | `validation_error` | Schema validation failed on the refund payload | -For the full wallet integration, including the read APIs and SDK behavior, see [Wallet](/developers/payments/wallet/). For the merchant dashboard workflow, see [Refund to Wallet (business docs)](/business/wallet#refund-to-wallet). +For the full wallet integration, including the read APIs and SDK behavior, see [M-Wallet](/developers/payments/wallet/). For the merchant dashboard workflow, see [Refund to M-Wallet (business docs)](/business/wallet#refund-to-wallet). ##### Void {#void} @@ -180,7 +180,7 @@ Select the operation to see its example payload and the full interactive API sch - + ```json title="Example Request" { diff --git a/docs/developers/payments/checkout-sdk/android.mdx b/docs/developers/payments/checkout-sdk/android.mdx index e96368d..c3348a0 100644 --- a/docs/developers/payments/checkout-sdk/android.mdx +++ b/docs/developers/payments/checkout-sdk/android.mdx @@ -706,7 +706,7 @@ The SDK supports multiple instances of onsite checkout payments. Therefore, for Fees are not displayed for onsite checkout instances due to the support of multiple card types through omni PG (multi-card) configurations. The presence of multiple payment icons also indicates this multi-card functionality. ::: -## Ottu Wallet +## M-Wallet {#ottu-wallet} When a customer has wallet credit in the session currency, the SDK renders a **Wallet** payment method automatically — no init config is required. Pass the `customer_id` when creating the session, and either include `'wallet'` in `formsOfPayment` or omit `formsOfPayment` to show all available methods. Wallet is hidden for authorize-only sessions. diff --git a/docs/developers/payments/checkout-sdk/flutter.mdx b/docs/developers/payments/checkout-sdk/flutter.mdx index 44ad3e1..7575495 100644 --- a/docs/developers/payments/checkout-sdk/flutter.mdx +++ b/docs/developers/payments/checkout-sdk/flutter.mdx @@ -791,7 +791,7 @@ The SDK supports multiple instances of onsite checkout payments. Therefore, for No fees are displayed for onsite checkout instances. This is due to the support for multiple multi-card (omni PG) configurations. The presence of multiple payment icons is used to indicate this multi-card feature. ::: -## Ottu Wallet +## M-Wallet {#ottu-wallet} When a customer has wallet credit in the session currency, the SDK renders a **Wallet** payment method automatically — no init config is required. Pass the `customer_id` when creating the session, and either include `'wallet'` in `formsOfPayment` or omit `formsOfPayment` to show all available methods. Wallet is hidden for authorize-only sessions. diff --git a/docs/developers/payments/checkout-sdk/ios.mdx b/docs/developers/payments/checkout-sdk/ios.mdx index e394614..b160732 100644 --- a/docs/developers/payments/checkout-sdk/ios.mdx +++ b/docs/developers/payments/checkout-sdk/ios.mdx @@ -679,7 +679,7 @@ The SDK supports multiple instances of onsite checkout payments. Therefore, for Fees are not shown for onsite checkout instances because the system supports payments through multiple cards (omni PG). The multiple payment icons indicate the availability of different card options. ::: -## Ottu Wallet +## M-Wallet {#ottu-wallet} When a customer has wallet credit in the session currency, the SDK renders a **Wallet** payment method automatically — no init config is required. Pass the `customer_id` when creating the session, and either include `'wallet'` in `formsOfPayment` or omit `formsOfPayment` to show all available methods. Wallet is hidden for authorize-only sessions. diff --git a/docs/developers/payments/checkout-sdk/web.mdx b/docs/developers/payments/checkout-sdk/web.mdx index 2052925..3fd864d 100644 --- a/docs/developers/payments/checkout-sdk/web.mdx +++ b/docs/developers/payments/checkout-sdk/web.mdx @@ -141,7 +141,7 @@ The available options for `formsOfPayment` are: - `stcPay`: A method where customers enter their mobile number and provide an OTP send to their mobile number to complete their payment. - `urPay`: A method where customers enter their mobile number and provide an OTP send to their mobile number to complete their payment. - `jazz`: Renders Ottu's onsite card form inline using the Jazz SDK, so customers enter their card details directly on the merchant page instead of being redirected. See [Onsite Checkout](#onsite-checkout) for setup details. -- `wallet`: The Ottu Wallet payment method that allows customers to spend their stored balance at checkout. +- `wallet`: The M-Wallet payment method that allows customers to spend their stored balance at checkout. #### displayMode _`string`_ @@ -932,7 +932,7 @@ Checkout.init({
urpay button in Web SDK
-### Ottu Wallet +### M-Wallet {#ottu-wallet} When a customer has wallet credit in the session currency, the SDK renders a **Wallet** payment method automatically — no init config is required. Pass the `customer_id` when creating the session, and either include `'wallet'` in `formsOfPayment` or omit `formsOfPayment` to show all available methods. Wallet is hidden for authorize-only sessions. diff --git a/docs/developers/payments/index.md b/docs/developers/payments/index.md index 776b085..540a551 100644 --- a/docs/developers/payments/index.md +++ b/docs/developers/payments/index.md @@ -30,11 +30,11 @@ Process payments directly when you manage your own UI — Apple Pay buttons, Goo [**Go to Native Payments →**](native-payments.md) -### Wallet (Stored Balance) +### M-Wallet (Stored Balance) {#wallet-stored-balance} Refund payments to a customer's wallet balance instead of returning funds via the original gateway. Customers can spend wallet credit at any future Ottu checkout in the same currency — fully or partially alongside another payment method. -[**Go to Wallet →**](wallet/index.mdx) +[**Go to M-Wallet →**](wallet/index.mdx) ### Sandbox & Test Cards diff --git a/docs/developers/payments/payment-methods.md b/docs/developers/payments/payment-methods.md index fd16593..5159f1c 100644 --- a/docs/developers/payments/payment-methods.md +++ b/docs/developers/payments/payment-methods.md @@ -27,8 +27,8 @@ Ottu offers SDKs and tools to speed up your integration. See [Getting Started](/ The Payment Methods API is a foundation for other Ottu APIs. It returns detailed information about each available payment method — supported operations, wallet integrations, currencies, and tokenization capabilities — giving you the data needed to create payment sessions with the right gateways. -:::tip Wallet as a payment method -When a customer has positive wallet balance in the session currency, a method with `type: "wallet"` appears in the response. See [Wallet](/developers/payments/wallet/) for the full integration. +:::tip M-Wallet as a payment method +When a customer has positive wallet balance in the session currency, a method with `type: "wallet"` appears in the response. See [M-Wallet](/developers/payments/wallet/) for the full integration. ::: ### Workflow diff --git a/docs/developers/payments/wallet/index.mdx b/docs/developers/payments/wallet/index.mdx index 0ecd5ac..8e57627 100644 --- a/docs/developers/payments/wallet/index.mdx +++ b/docs/developers/payments/wallet/index.mdx @@ -1,5 +1,5 @@ --- -title: Wallet +title: M-Wallet description: Customer wallet credits — refund, store, and spend balances at checkout. toc_min_heading_level: 2 toc_max_heading_level: 3 @@ -17,9 +17,9 @@ import { developerWalletScenariosSteps } from "@site/src/data/wallet-scenarios"; import FAQ, { FAQItem } from "@site/src/components/FAQ"; import { OTTU_CONNECT_BASE_URL } from "@site/src/constants/api"; -# Wallet +# M-Wallet -Wallet lets you refund customer payments to a stored balance keyed by merchant, customer, and currency, then let the customer spend that credit at any future Ottu checkout in the same currency. No PII is stored on the wallet service. +M-Wallet (merchant wallet) lets you refund customer payments to a stored balance keyed by merchant, customer, and currency, then let the customer spend that credit at any future Ottu checkout in the same currency. No PII is stored on the wallet service. Authentication for merchant-facing endpoints follows the standard [Ottu API key](/developers/getting-started/authentication) flow — the same key you use for the [Checkout API](../checkout-api). Service-to-service auth between Ottu Connect and the wallet service uses OAuth 2.0 internally and is invisible to merchants. @@ -222,7 +222,7 @@ A KWD wallet cannot be used to pay a SAR order, and vice versa. Each currency ma When a payment is settled with wallet credit, the payment details — returned by the [Checkout API retrieve](../checkout-api) call, the [PSQ inquiry](../psq), and [payment webhooks](../../webhooks/payment-events#response-payment-webhook-customer_wallet_transactions) — include a `customer_wallet_transactions` array. Each element is one wallet leg that contributed to the payment, so you can attribute every leg without a separate call to the wallet read APIs. -The key is omitted entirely when no wallet was involved. A single-wallet payment emits a one-element list; multi-wallet stacking (Ottu credit plus a loyalty wallet, for example) emits one element per provider. +The key is omitted entirely when no wallet was involved. A single-wallet payment emits a one-element list; multi-wallet stacking (M-Wallet credit plus a loyalty wallet, for example) emits one element per provider. | Field | Type | Description | |---|---|---| @@ -259,7 +259,7 @@ Use these fields to keep your own system in sync: - **Audit trail** — store `operation_id` against your order. Pass it to [Get Operation by ID](#api-reference) to pull every ledger entry the operation produced for a full audit record. - **System updates** — use `provider_code` to attribute the credit to the correct wallet program, and `created_at` / `modified_at` to timestamp the movement in your own ledger. -Stacked-wallet setups (e.g., Ottu wallet + Qitaf) emit one record per provider in the same payload; single-wallet payments emit a one-element list — so you can reconcile multi-wallet payments without polling the wallet read APIs. +Stacked-wallet setups (e.g., M-Wallet + Qitaf) emit one record per provider in the same payload; single-wallet payments emit a one-element list — so you can reconcile multi-wallet payments without polling the wallet read APIs. ## API Reference @@ -318,13 +318,13 @@ Three read APIs let you query wallet state from your backend. All three accept t No. Wallet credit does not expire. - The payment webhook payload includes a [`customer_wallet_transactions`](../../webhooks/payment-events#response-payment-webhook-customer_wallet_transactions) array — one entry per wallet provider that contributed to the payment. For stacked-wallet setups (e.g., Ottu wallet + Qitaf), each provider emits its own entry; single-wallet payments emit a one-element list. + The payment webhook payload includes a [`customer_wallet_transactions`](../../webhooks/payment-events#response-payment-webhook-customer_wallet_transactions) array — one entry per wallet provider that contributed to the payment. For stacked-wallet setups (e.g., M-Wallet + Qitaf), each provider emits its own entry; single-wallet payments emit a one-element list. ## What's Next? -- [Refund to Wallet (Operations)](../../operations#refund-to-wallet) — the refund API change that creates wallet credits. -- [Checkout SDK — Ottu Wallet section](../checkout-sdk/web#ottu-wallet) — how wallet appears in the SDK. +- [Refund to M-Wallet (Operations)](../../operations#refund-to-wallet) — the refund API change that creates wallet credits. +- [Checkout SDK — M-Wallet section](../checkout-sdk/web#ottu-wallet) — how wallet appears in the SDK. - [Payment Methods API](../payment-methods) — discovering available payment methods. -- [Wallet for merchants](/business/wallet/) — business-side documentation. +- [M-Wallet for merchants](/business/wallet/) — business-side documentation. diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 47f44dd..fd127ff 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -252,7 +252,6 @@ const config: Config = { excludeRoutes: [ "/404*", "/search*", - "/business/*", "/developers/reference/*", "/overview/changelog*", ], diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json index 86058c2..167a059 100644 --- a/mcp-server/package-lock.json +++ b/mcp-server/package-lock.json @@ -7,8 +7,10 @@ "": { "name": "ottu-docs-mcp-server", "version": "1.0.0", + "hasInstallScript": true, "dependencies": { - "docusaurus-plugin-mcp-server": "^0.11.0" + "docusaurus-plugin-mcp-server": "^0.11.0", + "patch-package": "^8.0.1" }, "engines": { "node": ">=20" @@ -508,6 +510,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -601,6 +609,21 @@ "ajv": "^8.8.2" } }, + "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", @@ -699,6 +722,18 @@ "node": "18 || 20 || >=22" } }, + "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.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -748,6 +783,24 @@ "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", @@ -807,6 +860,34 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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/chalk/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/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -856,6 +937,21 @@ "node": ">=6.0" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -880,6 +976,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1022,6 +1136,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1532,6 +1663,18 @@ ], "license": "BSD-3-Clause" }, + "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/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -1553,6 +1696,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -1696,6 +1848,18 @@ "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-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2177,6 +2341,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", @@ -2187,6 +2366,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -2217,6 +2405,24 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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", @@ -2298,6 +2504,25 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonfile": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", @@ -2310,6 +2535,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2319,6 +2553,15 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/loader-runner": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", @@ -3425,6 +3668,19 @@ ], "license": "MIT" }, + "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/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -3465,6 +3721,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -3549,6 +3814,15 @@ "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/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -3570,6 +3844,22 @@ "wrappy": "1" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", @@ -3628,6 +3918,49 @@ "node": ">= 0.8" } }, + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3669,6 +4002,18 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "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/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -4085,6 +4430,18 @@ } } }, + "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/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -4130,6 +4487,23 @@ "url": "https://opencollective.com/express" } }, + "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/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -4247,6 +4621,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/smol-toml": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", @@ -4450,6 +4833,27 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "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/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4852,6 +5256,21 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", diff --git a/mcp-server/package.json b/mcp-server/package.json index 074ea0a..cc3efa3 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -4,11 +4,13 @@ "private": true, "type": "module", "scripts": { + "postinstall": "patch-package", "start": "node server.mjs", "test": "node test.mjs" }, "dependencies": { - "docusaurus-plugin-mcp-server": "^0.11.0" + "docusaurus-plugin-mcp-server": "^0.11.0", + "patch-package": "^8.0.1" }, "engines": { "node": ">=20" diff --git a/mcp-server/patches/docusaurus-plugin-mcp-server+0.11.0.patch b/mcp-server/patches/docusaurus-plugin-mcp-server+0.11.0.patch new file mode 100644 index 0000000..42c6407 --- /dev/null +++ b/mcp-server/patches/docusaurus-plugin-mcp-server+0.11.0.patch @@ -0,0 +1,34 @@ +diff --git a/node_modules/docusaurus-plugin-mcp-server/dist/index.js b/node_modules/docusaurus-plugin-mcp-server/dist/index.js +index c712475..149d578 100644 +--- a/node_modules/docusaurus-plugin-mcp-server/dist/index.js ++++ b/node_modules/docusaurus-plugin-mcp-server/dist/index.js +@@ -299,19 +299,19 @@ function englishStemmer(word) { + } + function createSearchIndex() { + return new FlexSearch.Document({ +- // Use 'forward' tokenization to avoid OOM with large doc sets +- // See: https://github.com/scalvert/docusaurus-plugin-mcp-server/issues/11 +- tokenize: "forward", ++ // 'strict' tokenization (ottu patch): index whole (stemmed) words only, no ++ // prefix expansion. 'forward' over full page content ballooned the export to ++ // ~250MB+ and OOM'd the mcp service; 'strict' keeps it small. The custom ++ // encode() below still lowercases + stems, so queries match normalized words. ++ tokenize: "strict", + // Enable caching for faster repeated queries + cache: 100, + // Higher resolution = more granular ranking (1-9) +- resolution: 9, +- // Enable context for phrase/proximity matching +- context: { +- resolution: 2, +- depth: 2, +- bidirectional: true +- }, ++ // Lowered 9 -> 3 (ottu patch): smaller index, ample ranking for a docs site. ++ resolution: 3, ++ // Contextual proximity index removed (ottu patch): with depth:2 bidirectional ++ // over full page content it exploded the export to ~293MB and OOM'd the mcp ++ // service on deploy. Plain field search keeps the index a few MB. + // Apply stemming to normalize word forms + encode: (str) => { + const words = str.toLowerCase().split(/[\s\-_.,;:!?'"()[\]{}]+/); diff --git a/mcp-server/server.mjs b/mcp-server/server.mjs index 36e6acc..d1c3900 100644 --- a/mcp-server/server.mjs +++ b/mcp-server/server.mjs @@ -14,6 +14,14 @@ const DATA_DIR = path.join(__dirname, "data"); const DOCS_PATH = path.join(DATA_DIR, "docs.json"); const INDEX_PATH = path.join(DATA_DIR, "search-index.json"); +// The flexsearch search index (built by docusaurus-plugin-mcp-server) can balloon +// far beyond the docs themselves because of contextual indexing over full page +// content. Loading a multi-hundred-MB index OOMs this small service and fails the +// deploy readiness probe — taking the whole docs site's deployment down with it. +// Above this cap we skip loading the index and run search-degraded (doc lookups + +// webhook relay still work) instead of crashing. Shrink the index to restore search. +const MAX_INDEX_BYTES = 50 * 1024 * 1024; // 50 MB + // ── Webhook Relay ──────────────────────────────────────────────────────────── // In-memory store for webhook payloads, keyed by orderId. Clients poll for // updates via GET /:orderId/events.json?since= — SSE was dropped because @@ -198,13 +206,41 @@ async function handleSeedWallet(req, res) { // ── MCP Artifacts ──────────────────────────────────────────────────────────── +// Read a fetch body (web ReadableStream) into a UTF-8 string, returning null if +// the decoded byte count exceeds maxBytes. Counting decoded chunks (not the +// Content-Length header) is what makes the cap robust against gzip. +async function readCapped(body, maxBytes) { + if (!body) return null; + const reader = body.getReader(); + const chunks = []; + let total = 0; + try { + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + total += value.byteLength; + if (total > maxBytes) { + await reader.cancel(); + return null; + } + chunks.push(Buffer.from(value)); + } + } finally { + reader.releaseLock?.(); + } + return Buffer.concat(chunks).toString("utf8"); +} + async function fetchArtifacts() { const dataUrl = `${BASE_URL}/_mcp-data`; console.log(`Fetching MCP artifacts from ${dataUrl}...`); + // Bound each fetch so a stalled docs origin (e.g. mid-deploy) can't hang the + // "ready" transition forever — fetchWithRetry then advances to the next attempt. + const opts = { signal: AbortSignal.timeout(20000) }; const [docsRes, indexRes] = await Promise.all([ - fetch(`${dataUrl}/docs.json`), - fetch(`${dataUrl}/search-index.json`), + fetch(`${dataUrl}/docs.json`, opts), + fetch(`${dataUrl}/search-index.json`, opts), ]); if (!docsRes.ok || !indexRes.ok) { @@ -215,7 +251,20 @@ async function fetchArtifacts() { fs.mkdirSync(DATA_DIR, { recursive: true }); fs.writeFileSync(DOCS_PATH, await docsRes.text()); - fs.writeFileSync(INDEX_PATH, await indexRes.text()); + + // Guard against an oversized index (see MAX_INDEX_BYTES). Content-Length is + // unreliable — the docs origin gzips responses, so the header reports the + // compressed size while the body decompresses to something far larger. So we + // stream the (decoded) body and bail past the cap, bounding memory regardless. + const indexText = await readCapped(indexRes.body, MAX_INDEX_BYTES); + if (indexText === null) { + fs.writeFileSync(INDEX_PATH, "{}"); + console.warn( + `Search index exceeds the ${MAX_INDEX_BYTES / 1048576}MB cap — skipping load. MCP search is DEGRADED until the index is shrunk; doc lookups and webhook relay are unaffected.` + ); + } else { + fs.writeFileSync(INDEX_PATH, indexText); + } const docsSize = (fs.statSync(DOCS_PATH).size / 1024).toFixed(0); const indexSize = (fs.statSync(INDEX_PATH).size / 1024).toFixed(0); @@ -254,6 +303,19 @@ async function start() { let mcpServer = null; let ready = false; + // Build (or rebuild) the in-memory MCP server from the artifact files currently + // on disk. Reused by startup and by POST /refresh — so a refresh actually + // reloads the index instead of only re-downloading the files. + function buildMcpServer() { + mcpServer = new McpDocsServer({ + docsPath: DOCS_PATH, + indexPath: INDEX_PATH, + name: "ottu-docs", + baseUrl: BASE_URL, + }); + ready = true; + } + const httpServer = http.createServer(async (req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); @@ -280,6 +342,7 @@ async function start() { if (req.method === "POST" && url === "/refresh") { try { await fetchArtifacts(); + buildMcpServer(); // reload the in-memory index, not just the files on disk res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "refreshed" })); } catch (err) { @@ -342,13 +405,7 @@ async function start() { // Fetch artifacts in background — don't block webhook relay fetchWithRetry() .then(() => { - mcpServer = new McpDocsServer({ - docsPath: DOCS_PATH, - indexPath: INDEX_PATH, - name: "ottu-docs", - baseUrl: BASE_URL, - }); - ready = true; + buildMcpServer(); console.log("MCP server ready."); }) .catch((err) => { diff --git a/package-lock.json b/package-lock.json index f0aca98..412dadf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "public-docs", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { "@docusaurus/core": "^3.10.0", "@docusaurus/faster": "^3.10.0", @@ -33,6 +34,7 @@ "docusaurus-plugin-openapi-docs": "^5.0.0", "docusaurus-theme-openapi-docs": "^5.0.0", "openapi-to-postmanv2": "^6.0.0", + "patch-package": "^8.0.1", "postman-code-generators": "^2.1.0", "postman-collection": "^5.2.0", "tsx": "^4.21.0", @@ -8703,6 +8705,13 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -12938,6 +12947,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -14974,6 +14993,33 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -14998,6 +15044,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/katex": { "version": "0.16.33", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.33.tgz", @@ -18909,6 +18965,94 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/patch-package/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -23356,6 +23500,16 @@ "node": "^18.0.0 || >=20.0.0" } }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 221921b..1a9a345 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { + "postinstall": "patch-package", "docusaurus": "docusaurus", "fetch-api": "tsx scripts/fetch-api-schema.ts", "enrich-api": "tsx scripts/enrich-api-spec.ts", @@ -47,6 +48,7 @@ "docusaurus-plugin-openapi-docs": "^5.0.0", "docusaurus-theme-openapi-docs": "^5.0.0", "openapi-to-postmanv2": "^6.0.0", + "patch-package": "^8.0.1", "postman-code-generators": "^2.1.0", "postman-collection": "^5.2.0", "tsx": "^4.21.0", diff --git a/patches/docusaurus-plugin-mcp-server+0.11.0.patch b/patches/docusaurus-plugin-mcp-server+0.11.0.patch new file mode 100644 index 0000000..42c6407 --- /dev/null +++ b/patches/docusaurus-plugin-mcp-server+0.11.0.patch @@ -0,0 +1,34 @@ +diff --git a/node_modules/docusaurus-plugin-mcp-server/dist/index.js b/node_modules/docusaurus-plugin-mcp-server/dist/index.js +index c712475..149d578 100644 +--- a/node_modules/docusaurus-plugin-mcp-server/dist/index.js ++++ b/node_modules/docusaurus-plugin-mcp-server/dist/index.js +@@ -299,19 +299,19 @@ function englishStemmer(word) { + } + function createSearchIndex() { + return new FlexSearch.Document({ +- // Use 'forward' tokenization to avoid OOM with large doc sets +- // See: https://github.com/scalvert/docusaurus-plugin-mcp-server/issues/11 +- tokenize: "forward", ++ // 'strict' tokenization (ottu patch): index whole (stemmed) words only, no ++ // prefix expansion. 'forward' over full page content ballooned the export to ++ // ~250MB+ and OOM'd the mcp service; 'strict' keeps it small. The custom ++ // encode() below still lowercases + stems, so queries match normalized words. ++ tokenize: "strict", + // Enable caching for faster repeated queries + cache: 100, + // Higher resolution = more granular ranking (1-9) +- resolution: 9, +- // Enable context for phrase/proximity matching +- context: { +- resolution: 2, +- depth: 2, +- bidirectional: true +- }, ++ // Lowered 9 -> 3 (ottu patch): smaller index, ample ranking for a docs site. ++ resolution: 3, ++ // Contextual proximity index removed (ottu patch): with depth:2 bidirectional ++ // over full page content it exploded the export to ~293MB and OOM'd the mcp ++ // service on deploy. Plain field search keeps the index a few MB. + // Apply stemming to normalize word forms + encode: (str) => { + const words = str.toLowerCase().split(/[\s\-_.,;:!?'"()[\]{}]+/); diff --git a/sidebars.ts b/sidebars.ts index 8f03ee1..016037f 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -209,7 +209,7 @@ const sidebars: SidebarsConfig = { }, { type: "category", - label: "Wallet", + label: "M-Wallet", link: { type: "doc", id: "developers/payments/wallet/index" }, items: [ { @@ -601,12 +601,12 @@ const sidebars: SidebarsConfig = { }, { type: "category", - label: "Wallet", + label: "M-Wallet", link: { type: "doc", id: "business/wallet/index" }, items: [ { type: "link", - label: "Why use Wallet", + label: "Why use M-Wallet", href: "/business/wallet#why-use-wallet", }, { @@ -616,12 +616,12 @@ const sidebars: SidebarsConfig = { }, { type: "link", - label: "Refund to Wallet", + label: "Refund to M-Wallet", href: "/business/wallet#refund-to-wallet", }, { type: "link", - label: "Wallet at Checkout", + label: "M-Wallet at Checkout", href: "/business/wallet#wallet-at-checkout", }, { @@ -631,7 +631,7 @@ const sidebars: SidebarsConfig = { }, { type: "link", - label: "Wallet Reporting", + label: "M-Wallet Reporting", href: "/business/wallet#wallet-reporting", }, { diff --git a/src/components/WalletDemo/WalletDemoInner.tsx b/src/components/WalletDemo/WalletDemoInner.tsx index 5396ef5..8920142 100644 --- a/src/components/WalletDemo/WalletDemoInner.tsx +++ b/src/components/WalletDemo/WalletDemoInner.tsx @@ -168,7 +168,7 @@ export default function WalletDemoInner() {
{state.status === "idle" && (
-

Try Wallet at Checkout

+

Try M-Wallet at Checkout

Seed a fresh customer's wallet through the docs backend, then pay with it in the embedded SDK. No real charges. diff --git a/src/components/WalletDemo/index.tsx b/src/components/WalletDemo/index.tsx index 3c2e1e2..c63293a 100644 --- a/src/components/WalletDemo/index.tsx +++ b/src/components/WalletDemo/index.tsx @@ -8,7 +8,7 @@ export default function WalletDemo(): React.JSX.Element { fallback={

-

Try Wallet at Checkout

+

Try M-Wallet at Checkout

Loading…

diff --git a/src/diagrams/WalletFlowDiagram.tsx b/src/diagrams/WalletFlowDiagram.tsx index 819df17..53fab21 100644 --- a/src/diagrams/WalletFlowDiagram.tsx +++ b/src/diagrams/WalletFlowDiagram.tsx @@ -2,7 +2,7 @@ import React from "react"; import Diagram from "@site/src/components/Diagram"; /** - * Ottu wallet refund/reserve flow diagram. + * M-Wallet refund/reserve flow diagram. * * Single inline, theme-aware SVG generated by the `svg-diagram` skill * (scripts/inline-svg.py, slug "wallet-flow"). Scoped `