From 190a09e115e6e1082768f602cba2e813afe83093 Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:26:12 +0530 Subject: [PATCH 01/10] =?UTF-8?q?migrate=20to=20api.context.dev/v1=20?= =?UTF-8?q?=E2=80=94=2025=20operations,=20246=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- README.md | 243 +- credentials/BranddevApi.credentials.ts | 49 - credentials/ContextdevApi.credentials.ts | 38 + credentials/{branddev.svg => contextdev.svg} | 0 nodes/Branddev/Branddev.node.json | 18 - nodes/Branddev/Branddev.node.ts | 63 - nodes/Branddev/brand-dev-logo.svg | 36237 ---------------- .../resources/aiDataExtraction/index.ts | 105 - nodes/Branddev/resources/brand/index.ts | 581 - nodes/Branddev/resources/naics/index.ts | 123 - nodes/Branddev/resources/screenshot/index.ts | 233 - nodes/ContextDev/ContextDev.node.json | 17 + nodes/ContextDev/ContextDev.node.ts | 55 + nodes/ContextDev/context-dev-logo.svg | 1 + .../resources/aiDataExtraction/index.ts | 184 + nodes/ContextDev/resources/brand/index.ts | 270 + nodes/ContextDev/resources/industry/index.ts | 89 + nodes/ContextDev/resources/utility/index.ts | 71 + nodes/ContextDev/resources/web/brand.ts | 111 + nodes/ContextDev/resources/web/index.ts | 111 + nodes/ContextDev/resources/web/scraping.ts | 247 + .../ContextDev/resources/web/searchExtract.ts | 175 + package-lock.json | 9770 +++-- package.json | 31 +- tests/aiDataExtraction.test.ts | 212 + tests/brand.test.ts | 247 + tests/credentials.test.ts | 89 + tests/helpers.ts | 124 + tests/industry.test.ts | 147 + tests/node.test.ts | 280 + tests/utility.test.ts | 150 + tests/web.test.ts | 370 + 32 files changed, 9740 insertions(+), 40701 deletions(-) delete mode 100644 credentials/BranddevApi.credentials.ts create mode 100644 credentials/ContextdevApi.credentials.ts rename credentials/{branddev.svg => contextdev.svg} (100%) delete mode 100644 nodes/Branddev/Branddev.node.json delete mode 100644 nodes/Branddev/Branddev.node.ts delete mode 100644 nodes/Branddev/brand-dev-logo.svg delete mode 100644 nodes/Branddev/resources/aiDataExtraction/index.ts delete mode 100644 nodes/Branddev/resources/brand/index.ts delete mode 100644 nodes/Branddev/resources/naics/index.ts delete mode 100644 nodes/Branddev/resources/screenshot/index.ts create mode 100644 nodes/ContextDev/ContextDev.node.json create mode 100644 nodes/ContextDev/ContextDev.node.ts create mode 100644 nodes/ContextDev/context-dev-logo.svg create mode 100644 nodes/ContextDev/resources/aiDataExtraction/index.ts create mode 100644 nodes/ContextDev/resources/brand/index.ts create mode 100644 nodes/ContextDev/resources/industry/index.ts create mode 100644 nodes/ContextDev/resources/utility/index.ts create mode 100644 nodes/ContextDev/resources/web/brand.ts create mode 100644 nodes/ContextDev/resources/web/index.ts create mode 100644 nodes/ContextDev/resources/web/scraping.ts create mode 100644 nodes/ContextDev/resources/web/searchExtract.ts create mode 100644 tests/aiDataExtraction.test.ts create mode 100644 tests/brand.test.ts create mode 100644 tests/credentials.test.ts create mode 100644 tests/helpers.ts create mode 100644 tests/industry.test.ts create mode 100644 tests/node.test.ts create mode 100644 tests/utility.test.ts create mode 100644 tests/web.test.ts diff --git a/README.md b/README.md index 9854ad5..a84c81f 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,202 @@ -# n8n-nodes-branddev +# n8n-nodes-context-dev -This is an n8n community node that lets you interact with the [Brand.dev API](https://brand.dev) in your n8n workflows. +This is an n8n community node that lets you interact with the [Context.dev API](https://context.dev) in your n8n workflows. -Brand.dev provides comprehensive brand data including logos, colors, fonts, screenshots, styleguides, and company information for millions of brands worldwide. +Context.dev provides brand intelligence, web scraping, AI-powered data extraction, industry classification, and utility operations — covering millions of brands worldwide. [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/sustainable-use-license/) workflow automation platform. -[Installation](#installation) | [Operations](#operations) | [Credentials](#credentials) | [Compatibility](#compatibility) | [Resources](#resources) +[Installation](#installation) | [Operations](#operations) | [Credentials](#credentials) | [Compatibility](#compatibility) | [Differences from Zapier / Make](#differences-from-zapier--make) | [Migrating from n8n-nodes-branddev](#migrating-from-n8n-nodes-branddev) | [Resources](#resources) ## Installation Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation. -For the n8n desktop app, go to **Settings** > **Community Nodes** and search for `n8n-nodes-branddev`. +For the n8n desktop app, go to **Settings** > **Community Nodes** and search for `n8n-nodes-context-dev`. ## Operations -This node supports the following resources and operations: - -### Brand -- **Retrieve by Domain** - Get brand information by domain name - - Returns logos, colors, fonts, company info, social media, and more - - Supports 50+ languages via `force_language` parameter - - Optional speed optimization for faster responses - - Configurable timeout (1-300,000ms) - -- **Retrieve by Company Name** - Search for a company by name and retrieve brand data - - Fuzzy search for company names (3-30 characters) - - Returns comprehensive brand data - -- **Retrieve by Email** - Extract domain from email and retrieve brand data - - Excludes free email providers (gmail.com, yahoo.com, etc.) - - Blocks disposable email addresses - -- **Retrieve by Stock Ticker** - Look up company by stock ticker symbol - - Supports 30+ global stock exchanges (NASDAQ, NYSE, LSE, JPX, HKSE, etc.) - - Configurable exchange via `ticker_exchange` parameter - -- **Retrieve by ISIN** - Look up company by International Securities Identification Number - - Global securities identification - - Returns full brand data - -### Industry Classification (NAICS) -- **Classify Brand** - Get NAICS (North American Industry Classification System) codes for any brand - - Accepts domain or company name as input - - Returns 1-10 NAICS codes with confidence scores - - Configurable min/max results - - Supports detailed 6-digit NAICS codes - -### Screenshot / Styleguide -- **Take Screenshot** - Capture a viewport or full-page screenshot of any website - - Viewport or full-page screenshots - - Specific page types (login, pricing, careers, contact, blog, etc.) - - Quality vs speed optimization - - Returns high-quality screenshot URL - -- **Extract Styleguide** - Extract comprehensive design system including colors, typography, spacing, shadows, and components - - Color palettes with usage statistics - - Typography scales and font information - - Spacing and sizing systems - - Box shadows and border radius values - - Component patterns and styles - -- **Extract Fonts** - Get detailed font information including families, usage statistics, and fallbacks - - Font family detection - - Usage statistics (element count, word count) - - Fallback chains - - Font weights and styles +The node exposes five resources. Each resource groups related operations. + +### Brand Intelligence + +| Operation | Description | +|---|---| +| **Retrieve by Domain** | Get logos, colors, fonts, company info, social media, and more for any domain | +| **Retrieve Simplified** | Faster response returning only domain, title, colors, logos, and backdrops | +| **Retrieve by Company Name** | Search by company name and retrieve full brand data | +| **Retrieve by Email** | Extract domain from email address and retrieve brand data (free/disposable providers blocked) | +| **Retrieve by Stock Ticker** | Look up a company by stock ticker symbol across 12+ global exchanges | +| **Retrieve by ISIN** | Look up a company by International Securities Identification Number | +| **Identify From Transaction** | Match a merchant name from a bank statement to a brand | + +**Notable options:** `Force Language` (28 languages), `Max Speed` (faster response, less data), `Max Age` (cache freshness control). + +--- + +### Web Scraping + +| Operation | Description | +|---|---| +| **Scrape to Markdown** | Scrape any URL and get back clean markdown content | +| **Scrape HTML** | Scrape any URL and return the raw HTML | +| **Scrape Images** | Extract all images from a webpage including URLs, inline SVGs, and metadata | +| **Crawl Website** | Crawl multiple pages of a website and return markdown for each | +| **Crawl Sitemap** | Parse a domain sitemap and return all discovered page URLs | +| **Extract Structured Data** | Crawl a website and extract data matching a JSON schema you define | +| **Search** | Run a web search and get structured results with optional inline markdown | +| **Screenshot** | Capture a screenshot with options for page type (homepage, pricing, login, etc.) and full-page mode | +| **Extract Styleguide** | Extract colors, typography, spacing, shadows, and UI components from a website | +| **Extract Fonts** | Extract font families, usage statistics, fallbacks, and element counts | +| **Extract Competitors** | Identify direct competitors for a given brand domain | + +**Notable options:** `URL Regex` (filter crawl/sitemap URLs), `Max Depth` / `Max Pages` (crawl limits), `Stop After (Ms)` (time-budget for crawls), `Fact Check` (ground extraction in page content), `Full Page Screenshot`, `Handle Cookie Popup`, `Query Fanout` (broaden search recall), `Scrape Results to Markdown`. + +--- + +### AI Data Extraction + +| Operation | Description | +|---|---| +| **AI Query** | Define named data points and extract them from any domain using AI | +| **Extract Products** | Extract a product catalog (name, description, pricing, features) from a brand website | +| **Extract Single Product** | Extract structured product data from a specific product page URL | + +**AI Query** accepts typed data points (text, number, boolean, date, URL, list) and lets you target specific pages (homepage, pricing, about us, blog, careers, etc.). + +--- + +### Industry Classification + +| Operation | Description | +|---|---| +| **Classify NAICS** | Get 2022 NAICS (North American Industry Classification System) codes for a domain or company name | +| **Classify SIC** | Get SIC (Standard Industrial Classification) codes — supports the original 1987 dataset or the latest SEC dataset | + +Both operations accept a domain or company name, return results with confidence scores, and support configurable min/max result counts. + +--- + +### Utility + +| Operation | Description | +|---|---| +| **Prefetch by Domain** | Warm the cache for a domain before retrieval to reduce latency | +| **Prefetch by Email** | Same as Prefetch by Domain but accepts an email address (free/disposable emails rejected) | + +--- ## Credentials -To use this node, you need a Brand.dev API key: +To use this node, you need a Context.dev API key: -1. Sign up for a free account at [Brand.dev](https://brand.dev) -2. Go to your [dashboard](https://brand.dev/home) and copy your API key -4. In n8n, create new credentials: +1. Sign up for a free account at [Context.dev](https://context.dev) +2. Go to your [dashboard](https://app.context.dev) and copy your API key +3. In n8n, create new credentials: - Go to **Credentials** > **New** - - Search for "Brand.dev API" + - Search for "Context.dev API" - Paste your API key -The node will automatically test the credentials by making a test request to the Brand.dev API. +The node will automatically test the credentials by making a test request to the Context.dev API. ## Compatibility -This node has been tested with: - n8n version 1.0.0 and above -- Requires n8n-workflow as a peer dependency +- Requires `n8n-workflow` as a peer dependency ## Usage ### Example: Get Brand Logos and Colors -1. Add the Brand.dev node to your workflow -2. Select **Brand** as the resource -3. Choose **Retrieve by Domain** operation +1. Add the **Context.dev** node to your workflow +2. Select **Brand Intelligence** as the resource +3. Choose **Retrieve by Domain** 4. Enter a domain (e.g., `stripe.com`) -5. The node returns comprehensive brand data including: - - Logos (SVG, PNG formats with different variations) - - Brand colors (primary, accent, background) - - Company information - - Social media links +5. The node returns comprehensive brand data including logos (SVG/PNG), brand colors, company description, and social media links -### Example: Take Website Screenshots +### Example: Scrape a Website to Markdown -1. Select **Screenshot / Styleguide** as the resource -2. Choose **Take Screenshot** operation -3. Enter a domain and optionally enable full-page screenshot -4. Get a high-quality screenshot URL +1. Select **Web Scraping** as the resource +2. Choose **Scrape to Markdown** +3. Enter a URL (e.g., `https://stripe.com/pricing`) +4. Optionally enable **Main Content Only** to strip navigation and footers + +### Example: Extract Structured Data with AI + +1. Select **AI Data Extraction** as the resource +2. Choose **AI Query** +3. Enter a domain (e.g., `stripe.com`) +4. Add data points — for example: `company_tagline` (text), `has_free_tier` (boolean), `pricing_tiers` (list) +5. Optionally restrict analysis to specific pages such as Homepage or Pricing + +### Example: Crawl a Blog + +1. Select **Web Scraping** as the resource +2. Choose **Crawl Website** +3. Enter the site URL (e.g., `https://stripe.com/blog`) +4. Set a **URL Regex** like `^https://stripe\.com/blog/` to stay on-topic +5. Set **Max Pages** and **Stop After (Ms)** to control cost and time ### Tips -- Use the **Additional Fields** to customize API requests with optional parameters -- Combine with other n8n nodes to enrich your data pipelines -- The node supports n8n's usable as AI tool feature for AI-powered workflows +- Use **Additional Fields** to customize optional parameters on every operation +- The node supports n8n's **Usable as AI Tool** feature, making it compatible with AI agent workflows +- Combine **Extract Competitors** + **Retrieve by Domain** in a loop to build a competitive intelligence pipeline + +## Differences from Zapier / Make + +The Context.dev n8n node exposes **more operations** than the equivalent Zapier or Make integrations: + +- **Web Scraping** — Zapier/Make do not expose the full scraping suite (crawl, sitemap, markdown, HTML, images, search, structured extraction) +- **AI Data Extraction** — the AI Query and product extraction operations are n8n-exclusive +- **Utility** — prefetch operations for cache warming are available only in n8n +- **Industry Classification** — both NAICS and SIC classification are available; Make only exposes NAICS +- **Brand Intelligence** — all seven lookup methods (domain, simplified, name, email, ticker, ISIN, transaction) are available; Zapier exposes a subset + +## Migrating from n8n-nodes-branddev + +This package replaces `n8n-nodes-branddev`. The underlying service has migrated from Brand.dev to Context.dev. + +### What changed + +| | Old (`n8n-nodes-branddev`) | New (`n8n-nodes-context-dev`) | +|---|---|---| +| Package name | `n8n-nodes-branddev` | `n8n-nodes-context-dev` | +| Node name | Brand.dev | Context.dev | +| Credential type | Brand.dev API | Context.dev API | +| API base URL | `https://api.brand.dev/v1` | `https://api.context.dev/v1` | +| Resources | Brand, Industry Classification, Screenshot/Styleguide | Brand Intelligence, Web Scraping, AI Data Extraction, Industry Classification, Utility | + +### Migration steps + +1. In n8n, go to **Settings** > **Community Nodes** and install `n8n-nodes-context-dev` +2. Uninstall `n8n-nodes-branddev` +3. Create new **Context.dev API** credentials using your Context.dev API key (available at [app.context.dev](https://app.context.dev)) +4. In each workflow that used the old Brand.dev node, replace it with the new Context.dev node and re-select the equivalent resource and operation +5. Operations that existed in the old node are preserved under the **Brand Intelligence** resource with the same names — no parameter changes are required for those operations + +### Operations that moved resources + +The following operations were previously under **Screenshot / Styleguide** and are now under **Web Scraping**: + +- Take Screenshot → Screenshot +- Extract Styleguide → Extract Styleguide +- Extract Fonts → Extract Fonts + +## Development + +To run the test suite: + +```bash +npm test +``` ## Resources - [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/) -- [Brand.dev API Documentation](https://docs.brand.dev) -- [Brand.dev API Reference](https://docs.brand.dev/api-reference) -- [GitHub Repository](https://github.com/nikhilrado/brand-dev-n8n) +- [Context.dev API Documentation](https://docs.context.dev) +- [Context.dev API Reference](https://docs.context.dev/api-reference) +- [GitHub Repository](https://github.com/context-dot-dev/n8n-integration) ## License diff --git a/credentials/BranddevApi.credentials.ts b/credentials/BranddevApi.credentials.ts deleted file mode 100644 index e6e6feb..0000000 --- a/credentials/BranddevApi.credentials.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class BranddevApi implements ICredentialType { - name = 'branddevApi'; - - displayName = 'Brand.dev API'; - - documentationUrl = 'https://docs.brand.dev/quickstart'; - - icon = 'file:branddev.svg' as const; - - properties: INodeProperties[] = [ - { - displayName: 'API Key', - name: 'accessToken', - type: 'string', - typeOptions: { password: true }, - required: true, - default: '', - placeholder: 'brand_...', - description: 'API key from your Brand.dev account', - }, - ]; - - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - headers: { - Authorization: '=Bearer {{$credentials.accessToken}}', - 'integration_name': 'n8n', - }, - }, - }; - - test: ICredentialTestRequest = { - request: { - baseURL: 'https://api.brand.dev/v1', - url: '/brand/retrieve', - qs: { - domain: 'brand.dev', - }, - }, - }; -} diff --git a/credentials/ContextdevApi.credentials.ts b/credentials/ContextdevApi.credentials.ts new file mode 100644 index 0000000..06d073b --- /dev/null +++ b/credentials/ContextdevApi.credentials.ts @@ -0,0 +1,38 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class ContextdevApi implements ICredentialType { + name = 'contextdevApi'; + displayName = 'Context.dev API'; + icon = 'file:contextdev.svg' as const; + documentationUrl = 'https://docs.context.dev/guides/get-started/quickstart'; + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + default: '', + }, + ]; + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + integration_name: 'n8n', + }, + }, + }; + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api.context.dev/v1', + url: '/brand/retrieve', + qs: { domain: 'context.dev' }, + }, + }; +} diff --git a/credentials/branddev.svg b/credentials/contextdev.svg similarity index 100% rename from credentials/branddev.svg rename to credentials/contextdev.svg diff --git a/nodes/Branddev/Branddev.node.json b/nodes/Branddev/Branddev.node.json deleted file mode 100644 index 7bb62d9..0000000 --- a/nodes/Branddev/Branddev.node.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "node": "n8n-nodes-branddev", - "nodeVersion": "1.0", - "codexVersion": "1.0", - "categories": ["Development", "Marketing & Content"], - "resources": { - "credentialDocumentation": [ - { - "url": "https://docs.brand.dev/quickstart" - } - ], - "primaryDocumentation": [ - { - "url": "https://docs.brand.dev" - } - ] - } -} diff --git a/nodes/Branddev/Branddev.node.ts b/nodes/Branddev/Branddev.node.ts deleted file mode 100644 index 451929d..0000000 --- a/nodes/Branddev/Branddev.node.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { NodeConnectionTypes, type INodeType, type INodeTypeDescription } from 'n8n-workflow'; -import { brandDescription } from './resources/brand'; -import { naicsDescription } from './resources/naics'; -import { productDescription } from './resources/aiDataExtraction'; -import { screenshotStyleguideDescription } from './resources/screenshot'; - -export class Branddev implements INodeType { - description: INodeTypeDescription = { - displayName: 'Brand.dev', - name: 'branddev', - icon: 'file:brand-dev-logo.svg', - group: ['transform'], - version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Retrieve brand data from Brand.dev API', - defaults: { - name: 'Brand.dev', - }, - usableAsTool: true, - inputs: [NodeConnectionTypes.Main], - outputs: [NodeConnectionTypes.Main], - credentials: [{ name: 'branddevApi', required: true }], - requestDefaults: { - baseURL: 'https://api.brand.dev/v1', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'integration_name': 'n8n', - }, - }, - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Retrieve Brand', - value: 'brand', - }, - { - name: 'Industry Classification', - value: 'naics', - }, - { - name: 'AI Data Extraction', - value: 'aiDataExtraction', - }, - { - name: 'Screenshot / Styleguide', - value: 'screenshotStyleguide', - }, - ], - default: 'brand', - }, - ...brandDescription, - ...naicsDescription, - ...productDescription, - ...screenshotStyleguideDescription, - ], - }; -} diff --git a/nodes/Branddev/brand-dev-logo.svg b/nodes/Branddev/brand-dev-logo.svg deleted file mode 100644 index 4211223..0000000 --- a/nodes/Branddev/brand-dev-logo.svg +++ /dev/null @@ -1,36237 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nodes/Branddev/resources/aiDataExtraction/index.ts b/nodes/Branddev/resources/aiDataExtraction/index.ts deleted file mode 100644 index 0c45f61..0000000 --- a/nodes/Branddev/resources/aiDataExtraction/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const showOnlyForProduct = { - resource: ['aiDataExtraction'], -}; - -export const productDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: showOnlyForProduct, - }, - options: [ - { - name: 'Extract Products From a Brand\'s Website', - value: 'extractFromWebsite', - action: 'Extract products from a brand s website', - description: 'Extract product information from a brand\'s website including name, description, pricing, and more. View docs.', - routing: { - request: { - method: 'POST', - url: '/brand/ai/products', - }, - }, - }, - ], - default: 'extractFromWebsite', - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForProduct, - operation: ['extractFromWebsite'], - }, - }, - default: '', - placeholder: 'example.com', - description: 'The domain name to analyze for product extraction', - routing: { - request: { - body: { - domain: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...showOnlyForProduct, - operation: ['extractFromWebsite'], - }, - }, - options: [ - { - displayName: 'Max Products', - name: 'maxProducts', - type: 'number', - default: 5, - description: 'Maximum number of products to extract (1-12)', - typeOptions: { - minValue: 1, - maxValue: 12, - }, - routing: { - request: { - body: { - maxProducts: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Timeout in milliseconds for the request (1-300000ms)', - typeOptions: { - minValue: 1, - maxValue: 300000, - }, - routing: { - request: { - body: { - timeoutMS: '={{ $value }}', - }, - }, - }, - }, - ], - }, -]; diff --git a/nodes/Branddev/resources/brand/index.ts b/nodes/Branddev/resources/brand/index.ts deleted file mode 100644 index f4f29ed..0000000 --- a/nodes/Branddev/resources/brand/index.ts +++ /dev/null @@ -1,581 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const showOnlyForBrand = { - resource: ['brand'], -}; - -export const brandDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: showOnlyForBrand, - }, - options: [ - { - name: 'Identify From Transaction', - value: 'identifyByTransaction', - action: 'Identify brand from transaction data', - description: 'Identify brands from transaction information with optional geographic and industry context. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/transaction_identifier', - }, - }, - }, - { - name: 'Retrieve by Company Name', - value: 'retrieveByName', - action: 'Retrieve brand data by company name', - description: 'Search for company by name and retrieve brand data. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/retrieve-by-name', - }, - }, - }, - { - name: 'Retrieve by Domain', - value: 'retrieve', - action: 'Retrieve brand data by domain', - description: 'Get brand information including logos, colors, and more. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/retrieve', - }, - }, - }, - { - name: 'Retrieve by Email', - value: 'retrieveByEmail', - action: 'Retrieve brand data by email address', - description: 'Extract domain from email and retrieve brand data (excludes free/disposable emails). View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/retrieve-by-email', - }, - }, - }, - { - name: 'Retrieve by ISIN', - value: 'retrieveByIsin', - action: 'Retrieve brand data by ISIN', - description: 'Look up company by ISIN and retrieve brand data. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/retrieve-by-isin', - }, - }, - }, - { - name: 'Retrieve by Stock Ticker', - value: 'retrieveByTicker', - action: 'Retrieve brand data by stock ticker', - description: 'Look up company by stock ticker symbol and retrieve brand data. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/retrieve-by-ticker', - }, - }, - }, - { - name: 'Retrieve Simplified by Domain', - value: 'retrieveSimplified', - action: 'Retrieve simplified brand data by domain', - description: 'Get essential brand information (domain, title, colors, logos, backdrops) optimized for speed. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/retrieve-simplified', - }, - }, - }, - ], - default: 'retrieve', - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieve'], - }, - }, - default: '', - placeholder: 'example.com', - description: 'The domain to retrieve brand information for', - routing: { - request: { - qs: { - domain: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Company Name', - name: 'name', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieveByName'], - }, - }, - default: '', - placeholder: 'Apple Inc', - description: 'Company name to search for (3-30 characters)', - routing: { - request: { - qs: { - name: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Email', - name: 'email', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieveByEmail'], - }, - }, - default: '', - placeholder: 'contact@example.com', - description: 'Email address to extract domain from. Free email providers (gmail.com, yahoo.com) and disposable addresses are not allowed.', - routing: { - request: { - qs: { - email: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Stock Ticker', - name: 'ticker', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieveByTicker'], - }, - }, - default: '', - placeholder: 'AAPL', - description: 'Stock ticker symbol (1-15 characters, letters/numbers/dots only)', - routing: { - request: { - qs: { - ticker: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'ISIN', - name: 'isin', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieveByIsin'], - }, - }, - default: '', - placeholder: 'US0378331005', - description: 'International Securities Identification Number', - routing: { - request: { - qs: { - isin: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Domain', - name: 'simplifiedDomain', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieveSimplified'], - }, - }, - default: '', - placeholder: 'example.com', - description: 'The domain to retrieve simplified brand information for', - routing: { - request: { - qs: { - domain: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Transaction Title', - name: 'transaction_title', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['identifyByTransaction'], - }, - }, - default: '', - placeholder: 'STARBUCKS STORE #12345', - description: 'The transaction title or merchant name from the transaction data', - routing: { - request: { - qs: { - transaction_info: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...showOnlyForBrand, - operation: ['retrieve', 'retrieveByName', 'retrieveByEmail', 'retrieveByTicker', 'retrieveByIsin', 'retrieveSimplified', 'identifyByTransaction'], - }, - }, - options: [ - { - displayName: 'City', - name: 'city', - type: 'string', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: '', - placeholder: 'San Francisco', - description: 'City where the transaction occurred', - routing: { - request: { - qs: { - city: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Country', - name: 'country', - type: 'string', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: '', - placeholder: 'United States', - description: 'Country where the transaction occurred', - routing: { - request: { - qs: { - country: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Country GL', - name: 'country_gl', - type: 'string', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: '', - placeholder: 'us', - description: 'ISO 3166-1 alpha-2 country code for geographic search prioritization (e.g., us, gb, ca)', - routing: { - request: { - qs: { - country_gl: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Force Language', - name: 'force_language', - type: 'options', - default: 'english', - description: 'Force the language of the retrieved brand data', - options: [ - { name: 'Albanian', value: 'albanian' }, - { name: 'Arabic', value: 'arabic' }, - { name: 'Azeri', value: 'azeri' }, - { name: 'Bengali', value: 'bengali' }, - { name: 'Bulgarian', value: 'bulgarian' }, - { name: 'Cebuano', value: 'cebuano' }, - { name: 'Croatian', value: 'croatian' }, - { name: 'Czech', value: 'czech' }, - { name: 'Danish', value: 'danish' }, - { name: 'Dutch', value: 'dutch' }, - { name: 'English', value: 'english' }, - { name: 'Estonian', value: 'estonian' }, - { name: 'Farsi', value: 'farsi' }, - { name: 'Finnish', value: 'finnish' }, - { name: 'French', value: 'french' }, - { name: 'German', value: 'german' }, - { name: 'Hausa', value: 'hausa' }, - { name: 'Hawaiian', value: 'hawaiian' }, - { name: 'Hindi', value: 'hindi' }, - { name: 'Hungarian', value: 'hungarian' }, - { name: 'Icelandic', value: 'icelandic' }, - { name: 'Indonesian', value: 'indonesian' }, - { name: 'Italian', value: 'italian' }, - { name: 'Kazakh', value: 'kazakh' }, - { name: 'Kyrgyz', value: 'kyrgyz' }, - { name: 'Latin', value: 'latin' }, - { name: 'Latvian', value: 'latvian' }, - { name: 'Lithuanian', value: 'lithuanian' }, - { name: 'Macedonian', value: 'macedonian' }, - { name: 'Mongolian', value: 'mongolian' }, - { name: 'Nepali', value: 'nepali' }, - { name: 'Norwegian', value: 'norwegian' }, - { name: 'Pashto', value: 'pashto' }, - { name: 'Pidgin', value: 'pidgin' }, - { name: 'Polish', value: 'polish' }, - { name: 'Portuguese', value: 'portuguese' }, - { name: 'Romanian', value: 'romanian' }, - { name: 'Russian', value: 'russian' }, - { name: 'Serbian', value: 'serbian' }, - { name: 'Slovak', value: 'slovak' }, - { name: 'Slovenian', value: 'slovenian' }, - { name: 'Spanish', value: 'spanish' }, - { name: 'Swedish', value: 'swedish' }, - { name: 'Turkish', value: 'turkish' }, - { name: 'Ukrainian', value: 'ukrainian' }, - { name: 'Uzbek', value: 'uzbek' }, - { name: 'Vietnamese', value: 'vietnamese' }, - ], - routing: { - request: { - qs: { - force_language: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Latitude', - name: 'latitude', - type: 'number', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: 0, - placeholder: '37.7749', - description: 'Latitude coordinate of the transaction location', - routing: { - request: { - qs: { - latitude: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Longitude', - name: 'longitude', - type: 'number', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: 0, - placeholder: '-122.4194', - description: 'Longitude coordinate of the transaction location', - routing: { - request: { - qs: { - longitude: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'MCC (Merchant Category Code)', - name: 'mcc', - type: 'string', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: '', - placeholder: '5814', - description: 'Merchant Category Code (4-digit code) for business category context', - routing: { - request: { - qs: { - mcc: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Postal Code', - name: 'postal_code', - type: 'string', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: '', - placeholder: '94103', - description: 'Postal or ZIP code of the transaction location', - routing: { - request: { - qs: { - postal_code: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Speed Optimized', - name: 'speed_optimized', - type: 'boolean', - default: false, - description: 'Whether to optimize for speed by skipping time-consuming operations. Results in faster response but less comprehensive data.', - routing: { - request: { - qs: { - speed_optimized: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'State', - name: 'state', - type: 'string', - displayOptions: { - show: { - '/operation': ['identifyByTransaction'], - }, - }, - default: '', - placeholder: 'CA', - description: 'State or province where the transaction occurred', - routing: { - request: { - qs: { - state: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Ticker Exchange', - name: 'ticker_exchange', - type: 'options', - displayOptions: { - show: { - '/operation': ['retrieveByTicker'], - }, - }, - default: 'AMEX', - description: 'Stock exchange for the ticker (defaults to NASDAQ if not specified)', - options: [ - { name: 'AMEX', value: 'AMEX' }, - { name: 'AMS (Amsterdam)', value: 'AMS' }, - { name: 'ASX (Australia)', value: 'ASX' }, - { name: 'BME (Madrid)', value: 'BME' }, - { name: 'BSE (Bombay)', value: 'BSE' }, - { name: 'BVC (Colombia)', value: 'BVC' }, - { name: 'CBOE', value: 'CBOE' }, - { name: 'CPH (Copenhagen)', value: 'CPH' }, - { name: 'DFM (Dubai)', value: 'DFM' }, - { name: 'FSX (Frankfurt)', value: 'FSX' }, - { name: 'HKSE (Hong Kong)', value: 'HKSE' }, - { name: 'ICE', value: 'ICE' }, - { name: 'IST (Istanbul)', value: 'IST' }, - { name: 'JPX (Tokyo)', value: 'JPX' }, - { name: 'KSC (Korea)', value: 'KSC' }, - { name: 'LSE (London)', value: 'LSE' }, - { name: 'MCX (Moscow)', value: 'MCX' }, - { name: 'MEX (Mexico)', value: 'MEX' }, - { name: 'MIL (Milan)', value: 'MIL' }, - { name: 'NASDAQ', value: 'NASDAQ' }, - { name: 'NSE (India)', value: 'NSE' }, - { name: 'NYSE', value: 'NYSE' }, - { name: 'NZX (New Zealand)', value: 'NZX' }, - { name: 'OSL (Oslo)', value: 'OSL' }, - { name: 'SGX (Singapore)', value: 'SGX' }, - { name: 'SHZ (Shenzhen)', value: 'SHZ' }, - { name: 'SSE (Shanghai)', value: 'SSE' }, - { name: 'STO (Stockholm)', value: 'STO' }, - { name: 'SWX (Switzerland)', value: 'SWX' }, - { name: 'TSX (Toronto)', value: 'TSX' }, - { name: 'TWSE (Taiwan)', value: 'TWSE' }, - { name: 'VSE (Vienna)', value: 'VSE' }, - ], - routing: { - request: { - qs: { - ticker_exchange: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Timeout in milliseconds for the request (1-300000ms)', - typeOptions: { - minValue: 1, - maxValue: 300000, - }, - routing: { - request: { - qs: { - timeoutMS: '={{ $value }}', - }, - }, - }, - }, - ], - }, -]; diff --git a/nodes/Branddev/resources/naics/index.ts b/nodes/Branddev/resources/naics/index.ts deleted file mode 100644 index d7a8438..0000000 --- a/nodes/Branddev/resources/naics/index.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const showOnlyForNaics = { - resource: ['naics'], -}; - -export const naicsDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: showOnlyForNaics, - }, - options: [ - { - name: 'Classify Brand', - value: 'classify', - action: 'Retrieve NAICS code for any brand', - description: 'Classify any brand into a 2022 NAICS code. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/naics', - }, - }, - }, - ], - default: 'classify', - }, - { - displayName: 'Input', - name: 'input', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForNaics, - operation: ['classify'], - }, - }, - default: '', - placeholder: 'example.com or Apple Inc', - description: 'Brand domain or company name to classify. If a valid domain is provided, it will be used; otherwise, we will search for the brand using the provided title.', - routing: { - request: { - qs: { - input: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...showOnlyForNaics, - operation: ['classify'], - }, - }, - options: [ - { - displayName: 'Min Results', - name: 'minResults', - type: 'number', - default: 1, - description: 'Minimum number of NAICS codes to return (1-10)', - typeOptions: { - minValue: 1, - maxValue: 10, - }, - routing: { - request: { - qs: { - minResults: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Max Results', - name: 'maxResults', - type: 'number', - default: 5, - description: 'Maximum number of NAICS codes to return (1-10)', - typeOptions: { - minValue: 1, - maxValue: 10, - }, - routing: { - request: { - qs: { - maxResults: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Timeout in milliseconds for the request (1-300000ms)', - typeOptions: { - minValue: 1, - maxValue: 300000, - }, - routing: { - request: { - qs: { - timeoutMS: '={{ $value }}', - }, - }, - }, - }, - ], - }, -]; diff --git a/nodes/Branddev/resources/screenshot/index.ts b/nodes/Branddev/resources/screenshot/index.ts deleted file mode 100644 index 1653b01..0000000 --- a/nodes/Branddev/resources/screenshot/index.ts +++ /dev/null @@ -1,233 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const showOnlyForScreenshot = { - resource: ['screenshotStyleguide'], -}; - -export const screenshotStyleguideDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: showOnlyForScreenshot, - }, - options: [ - { - name: 'Take Screenshot', - value: 'capture', - action: 'Take screenshot of website', - description: 'Capture a screenshot of a website (viewport or full page). View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/screenshot', - }, - }, - }, - { - name: 'Extract Styleguide', - value: 'extractStyleguide', - action: 'Extract design system and styleguide from website', - description: 'Extract comprehensive design system including colors, typography, spacing, shadows, and components. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/styleguide', - }, - }, - }, - { - name: 'Extract Fonts', - value: 'extractFonts', - action: 'Extract font information from website', - description: 'Extract font information including font families, usage statistics, fallbacks, and element/word counts. View docs.', - routing: { - request: { - method: 'GET', - url: '/brand/fonts', - }, - }, - }, - ], - default: 'capture', - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - required: true, - displayOptions: { - show: { - ...showOnlyForScreenshot, - operation: ['capture', 'extractStyleguide', 'extractFonts'], - }, - }, - default: '', - placeholder: 'example.com', - description: 'Domain name to take screenshot of or extract styleguide from', - routing: { - request: { - qs: { - domain: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...showOnlyForScreenshot, - operation: ['capture'], - }, - }, - options: [ - { - displayName: 'Full Screenshot', - name: 'fullScreenshot', - type: 'boolean', - default: false, - description: 'Whether to take a full page screenshot. If false, takes a viewport screenshot (standard browser view).', - routing: { - request: { - qs: { - fullScreenshot: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Page Type', - name: 'page', - type: 'options', - default: 'login', - description: 'Specific page type to screenshot. System will find the most appropriate URL for this page type.', - options: [ - { name: 'Blog', value: 'blog' }, - { name: 'Careers', value: 'careers' }, - { name: 'Contact', value: 'contact' }, - { name: 'Login', value: 'login' }, - { name: 'Pricing', value: 'pricing' }, - { name: 'Privacy', value: 'privacy' }, - { name: 'Signup', value: 'signup' }, - { name: 'Terms', value: 'terms' }, - ], - routing: { - request: { - qs: { - page: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Prioritize', - name: 'prioritize', - type: 'options', - default: 'quality', - description: 'Screenshot capture priority', - options: [ - { name: 'Speed', value: 'speed' }, - { name: 'Quality', value: 'quality' }, - ], - routing: { - request: { - qs: { - prioritize: '={{ $value }}', - }, - }, - }, - }, - ], - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...showOnlyForScreenshot, - operation: ['extractStyleguide'], - }, - }, - options: [ - { - displayName: 'Prioritize', - name: 'prioritize', - type: 'options', - default: 'quality', - description: 'Screenshot capture priority for styleguide extraction', - options: [ - { name: 'Speed', value: 'speed' }, - { name: 'Quality', value: 'quality' }, - ], - routing: { - request: { - qs: { - prioritize: '={{ $value }}', - }, - }, - }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Timeout in milliseconds for the request (1-300000ms)', - typeOptions: { - minValue: 1, - maxValue: 300000, - }, - routing: { - request: { - qs: { - timeoutMS: '={{ $value }}', - }, - }, - }, - }, - ], - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...showOnlyForScreenshot, - operation: ['extractFonts'], - }, - }, - options: [ - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Timeout in milliseconds for the request (1-300000ms)', - typeOptions: { - minValue: 1, - maxValue: 300000, - }, - routing: { - request: { - qs: { - timeoutMS: '={{ $value }}', - }, - }, - }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/ContextDev.node.json b/nodes/ContextDev/ContextDev.node.json new file mode 100644 index 0000000..bf79709 --- /dev/null +++ b/nodes/ContextDev/ContextDev.node.json @@ -0,0 +1,17 @@ +{ + "node": "n8n-nodes-context-dev.contextDev", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": ["Data & Storage"], + "subcategories": { + "Data & Storage": ["Content & Files"] + }, + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.context.dev" + } + ] + }, + "alias": ["context.dev", "brand intelligence", "web scraping", "scrape"] +} diff --git a/nodes/ContextDev/ContextDev.node.ts b/nodes/ContextDev/ContextDev.node.ts new file mode 100644 index 0000000..ad8270c --- /dev/null +++ b/nodes/ContextDev/ContextDev.node.ts @@ -0,0 +1,55 @@ +import { NodeConnectionTypes, type INodeType, type INodeTypeDescription } from 'n8n-workflow'; + +import { brandDescription } from './resources/brand'; +import { webDescription } from './resources/web'; +import { aiDataExtractionDescription } from './resources/aiDataExtraction'; +import { industryDescription } from './resources/industry'; +import { utilityDescription } from './resources/utility'; + +export class ContextDev implements INodeType { + description: INodeTypeDescription = { + displayName: 'Context.dev', + name: 'contextDev', + icon: 'file:context-dev-logo.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', + description: 'Web scraping, brand intelligence, and AI data extraction via Context.dev', + defaults: { + name: 'Context.dev', + }, + usableAsTool: true, + inputs: [NodeConnectionTypes.Main], + outputs: [NodeConnectionTypes.Main], + credentials: [{ name: 'contextdevApi', required: true }], + requestDefaults: { + baseURL: 'https://api.context.dev/v1', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + integration_name: 'n8n', + }, + }, + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { name: 'AI Data Extraction', value: 'aiDataExtraction' }, + { name: 'Brand Intelligence', value: 'brand' }, + { name: 'Industry Classification', value: 'industry' }, + { name: 'Utility', value: 'utility' }, + { name: 'Web Scraping', value: 'web' }, + ], + default: 'brand', + }, + ...brandDescription, + ...webDescription, + ...aiDataExtractionDescription, + ...industryDescription, + ...utilityDescription, + ], + }; +} diff --git a/nodes/ContextDev/context-dev-logo.svg b/nodes/ContextDev/context-dev-logo.svg new file mode 100644 index 0000000..ebf073a --- /dev/null +++ b/nodes/ContextDev/context-dev-logo.svg @@ -0,0 +1 @@ + diff --git a/nodes/ContextDev/resources/aiDataExtraction/index.ts b/nodes/ContextDev/resources/aiDataExtraction/index.ts new file mode 100644 index 0000000..f49aa85 --- /dev/null +++ b/nodes/ContextDev/resources/aiDataExtraction/index.ts @@ -0,0 +1,184 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['aiDataExtraction'] }; + +export const aiDataExtractionDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show }, + options: [ + { + name: 'AI Query', + value: 'aiQuery', + action: 'Query a website with AI and extract structured data', + description: + 'Define data points to extract from any domain. View docs.', + routing: { request: { method: 'POST', url: '/brand/ai/query' } }, + }, + { + name: 'Extract Products', + value: 'extractProducts', + action: 'Extract products from a brand website', + description: + 'Extract product catalog with name, description, pricing, features. View docs.', + routing: { request: { method: 'POST', url: '/brand/ai/products' } }, + }, + { + name: 'Extract Single Product', + value: 'extractProduct', + action: 'Extract a single product from a URL', + description: 'Extract product data from a specific product page. View docs.', + routing: { request: { method: 'POST', url: '/brand/ai/product' } }, + }, + ], + default: 'aiQuery', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['aiQuery', 'extractProducts'] } }, + default: '', + placeholder: 'stripe.com', + routing: { request: { body: { domain: '={{ $value }}' } } }, + }, + { + displayName: 'Product URL', + name: 'url', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['extractProduct'] } }, + default: '', + placeholder: 'https://example.com/products/widget', + description: 'Direct URL to the product page', + routing: { request: { body: { url: '={{ $value }}' } } }, + }, + { + displayName: 'Data Points to Extract', + name: 'data_to_extract', + type: 'fixedCollection', + required: true, + typeOptions: { multipleValues: true }, + displayOptions: { show: { ...show, operation: ['aiQuery'] } }, + default: {}, + description: 'Define the structured data fields you want to extract from the website', + options: [ + { + name: 'datapoint', + displayName: 'Data Point', + values: [ + { + displayName: 'Description', + name: 'datapoint_description', + type: 'string', + default: '', + placeholder: 'The main tagline shown on the homepage', + }, + { + displayName: 'Example Value', + name: 'datapoint_example', + type: 'string', + default: '', + placeholder: 'The platform for modern teams', + }, + { + displayName: 'List Item Type', + name: 'datapoint_list_type', + type: 'options', + default: 'string', + options: [ + { name: 'Boolean', value: 'boolean' }, + { name: 'Date', value: 'date' }, + { name: 'Number', value: 'number' }, + { name: 'Object', value: 'object' }, + { name: 'String', value: 'string' }, + { name: 'URL', value: 'url' }, + ] + }, + { + displayName: 'Name', + name: 'datapoint_name', + type: 'string', + default: '', + placeholder: 'company_tagline', + }, + { + displayName: 'Type', + name: 'datapoint_type', + type: 'options', + default: 'text', + options: [ + { name: 'Boolean', value: 'boolean' }, + { name: 'Date', value: 'date' }, + { name: 'List', value: 'list' }, + { name: 'Number', value: 'number' }, + { name: 'Text', value: 'text' }, + { name: 'URL', value: 'url' }, + ] + }, + ], + }, + ], + routing: { request: { body: { data_to_extract: '={{ $value.datapoint }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { ...show, operation: ['aiQuery', 'extractProducts', 'extractProduct'] }, + }, + options: [ + { + displayName: 'Max Products', + name: 'maxProducts', + type: 'number', + displayOptions: { show: { '/operation': ['extractProducts'] } }, + default: 5, + typeOptions: { minValue: 1 }, + routing: { request: { body: { maxProducts: '={{ $value }}' } } }, + }, + { + displayName: 'Pages to Analyze', + name: 'specific_pages', + type: 'multiOptions', + displayOptions: { show: { '/operation': ['aiQuery'] } }, + default: [], + description: 'Specific pages to analyze on the website', + options: [ + { name: 'About Us', value: 'about_us' }, + { name: 'Blog', value: 'blog' }, + { name: 'Careers', value: 'careers' }, + { name: 'Contact Us', value: 'contact_us' }, + { name: 'FAQ', value: 'faq' }, + { name: 'Home Page', value: 'home_page' }, + { name: 'Pricing', value: 'pricing' }, + { name: 'Privacy Policy', value: 'privacy_policy' }, + { name: 'Terms & Conditions', value: 'terms_and_conditions' }, + ], + routing: { + request: { + body: { + specific_pages: + '={{ Object.fromEntries($value.map((k) => [k, true])) }}', + }, + }, + }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + default: 30000, + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/brand/index.ts b/nodes/ContextDev/resources/brand/index.ts new file mode 100644 index 0000000..6454cf7 --- /dev/null +++ b/nodes/ContextDev/resources/brand/index.ts @@ -0,0 +1,270 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['brand'] }; + +export const brandDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show }, + options: [ + { + name: 'Identify From Transaction', + value: 'identifyFromTransaction', + action: 'Identify a brand from transaction data', + description: + 'Match merchant name from bank statement to a brand. View docs.', + routing: { request: { method: 'GET', url: '/brand/transaction_identifier' } }, + }, + { + name: 'Retrieve by Company Name', + value: 'retrieveByName', + action: 'Retrieve brand data by company name', + description: + 'Search by company name and retrieve brand data. View docs.', + routing: { request: { method: 'GET', url: '/brand/retrieve-by-name' } }, + }, + { + name: 'Retrieve by Domain', + value: 'retrieve', + action: 'Retrieve brand data by domain', + description: + 'Get logos, colors, description and more. View docs.', + routing: { request: { method: 'GET', url: '/brand/retrieve' } }, + }, + { + name: 'Retrieve by Email', + value: 'retrieveByEmail', + action: 'Retrieve brand data by email address', + description: + 'Extracts domain from email (excludes free/disposable). View docs.', + routing: { request: { method: 'GET', url: '/brand/retrieve-by-email' } }, + }, + { + name: 'Retrieve by ISIN', + value: 'retrieveByIsin', + action: 'Retrieve brand data by ISIN', + description: + 'Look up by International Securities Identification Number. View docs.', + routing: { request: { method: 'GET', url: '/brand/retrieve-by-isin' } }, + }, + { + name: 'Retrieve by Stock Ticker', + value: 'retrieveByTicker', + action: 'Retrieve brand data by stock ticker', + description: + 'Look up by stock ticker symbol. View docs.', + routing: { request: { method: 'GET', url: '/brand/retrieve-by-ticker' } }, + }, + { + name: 'Retrieve Simplified', + value: 'retrieveSimplified', + action: 'Retrieve simplified brand data', + description: + 'Faster response with only domain, title, colors, logos, backdrops. View docs.', + routing: { request: { method: 'GET', url: '/brand/retrieve-simplified' } }, + }, + ], + default: 'retrieve', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['retrieve', 'retrieveSimplified'] } }, + default: '', + placeholder: 'stripe.com', + routing: { request: { qs: { domain: '={{ $value }}' } } }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['retrieveByEmail'] } }, + default: '', + placeholder: 'contact@stripe.com', + description: 'Free email providers (gmail.com, yahoo.com) and disposable addresses are not allowed', + routing: { request: { qs: { email: '={{ $value }}' } } }, + }, + { + displayName: 'Company Name', + name: 'name', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['retrieveByName'] } }, + default: '', + placeholder: 'Stripe', + routing: { request: { qs: { name: '={{ $value }}' } } }, + }, + { + displayName: 'Stock Ticker', + name: 'ticker', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['retrieveByTicker'] } }, + default: '', + placeholder: 'AAPL', + routing: { request: { qs: { ticker: '={{ $value }}' } } }, + }, + { + displayName: 'ISIN', + name: 'isin', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['retrieveByIsin'] } }, + default: '', + placeholder: 'US0378331005', + routing: { request: { qs: { isin: '={{ $value }}' } } }, + }, + { + displayName: 'Transaction Info', + name: 'transaction_info', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['identifyFromTransaction'] } }, + default: '', + placeholder: 'STARBUCKS STORE #12345', + description: 'Merchant name as it appears on a bank statement', + routing: { request: { qs: { transaction_info: '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + ...show, + operation: [ + 'retrieve', + 'retrieveByEmail', + 'retrieveByName', + 'retrieveByTicker', + 'retrieveByIsin', + 'retrieveSimplified', + 'identifyFromTransaction', + ], + }, + }, + options: [ + { + displayName: 'City', + name: 'city', + type: 'string', + displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, + default: '', + placeholder: 'San Francisco', + routing: { request: { qs: { city: '={{ $value }}' } } }, + }, + { + displayName: 'Country Code', + name: 'country_gl', + type: 'string', + displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, + default: '', + placeholder: 'us', + description: 'ISO 3166-1 alpha-2 country code', + routing: { request: { qs: { country_gl: '={{ $value }}' } } }, + }, + { + displayName: 'Force Language', + name: 'force_language', + type: 'options', + default: 'english', + options: [ + { name: 'Arabic', value: 'arabic' }, + { name: 'Chinese', value: 'chinese' }, + { name: 'Czech', value: 'czech' }, + { name: 'Danish', value: 'danish' }, + { name: 'Dutch', value: 'dutch' }, + { name: 'English', value: 'english' }, + { name: 'Finnish', value: 'finnish' }, + { name: 'French', value: 'french' }, + { name: 'German', value: 'german' }, + { name: 'Greek', value: 'greek' }, + { name: 'Hebrew', value: 'hebrew' }, + { name: 'Hindi', value: 'hindi' }, + { name: 'Hungarian', value: 'hungarian' }, + { name: 'Indonesian', value: 'indonesian' }, + { name: 'Italian', value: 'italian' }, + { name: 'Japanese', value: 'japanese' }, + { name: 'Korean', value: 'korean' }, + { name: 'Norwegian', value: 'norwegian' }, + { name: 'Polish', value: 'polish' }, + { name: 'Portuguese', value: 'portuguese' }, + { name: 'Romanian', value: 'romanian' }, + { name: 'Russian', value: 'russian' }, + { name: 'Spanish', value: 'spanish' }, + { name: 'Swedish', value: 'swedish' }, + { name: 'Thai', value: 'thai' }, + { name: 'Turkish', value: 'turkish' }, + { name: 'Ukrainian', value: 'ukrainian' }, + { name: 'Vietnamese', value: 'vietnamese' }, + ], + routing: { request: { qs: { force_language: '={{ $value }}' } } }, + }, + { + displayName: 'Max Age (Ms)', + name: 'maxAgeMs', + type: 'number', + default: 7776000000, + description: 'Max age of cached data in ms before a hard refresh. Default 3 months.', + typeOptions: { minValue: 86400000, maxValue: 31536000000 }, + routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether to skip time-consuming operations for a faster response at the cost of less data', + routing: { request: { qs: { maxSpeed: '={{ $value }}' } } }, + }, + { + displayName: 'MCC', + name: 'mcc', + type: 'string', + displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, + default: '', + placeholder: '5814', + description: 'Merchant Category Code', + routing: { request: { qs: { mcc: '={{ $value }}' } } }, + }, + { + displayName: 'Ticker Exchange', + name: 'ticker_exchange', + type: 'options', + displayOptions: { show: { '/operation': ['retrieveByTicker'] } }, + default: 'NASDAQ', + options: [ + { name: 'AMEX', value: 'AMEX' }, + { name: 'ASX (Australia)', value: 'ASX' }, + { name: 'FSX (Frankfurt)', value: 'FSX' }, + { name: 'HKSE (Hong Kong)', value: 'HKSE' }, + { name: 'JPX (Tokyo)', value: 'JPX' }, + { name: 'LSE (London)', value: 'LSE' }, + { name: 'NASDAQ', value: 'NASDAQ' }, + { name: 'NSE (India)', value: 'NSE' }, + { name: 'NYSE', value: 'NYSE' }, + { name: 'SGX (Singapore)', value: 'SGX' }, + { name: 'SSE (Shanghai)', value: 'SSE' }, + { name: 'TSX (Toronto)', value: 'TSX' }, + ], + routing: { request: { qs: { ticker_exchange: '={{ $value }}' } } }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + default: 30000, + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/industry/index.ts b/nodes/ContextDev/resources/industry/index.ts new file mode 100644 index 0000000..e10d413 --- /dev/null +++ b/nodes/ContextDev/resources/industry/index.ts @@ -0,0 +1,89 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['industry'] }; + +export const industryDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show }, + options: [ + { + name: 'Classify NAICS', + value: 'retrieveNaics', + action: 'Classify a brand into NAICS industry codes', + description: + '2022 NAICS codes from domain or company name. View docs.', + routing: { request: { method: 'GET', url: '/web/naics' } }, + }, + { + name: 'Classify SIC', + value: 'retrieveSic', + action: 'Classify a brand into SIC industry codes', + description: + 'Standard Industrial Classification codes from domain or company name. View docs.', + routing: { request: { method: 'GET', url: '/web/sic' } }, + }, + ], + default: 'retrieveNaics', + }, + { + displayName: 'Input', + name: 'input', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['retrieveNaics', 'retrieveSic'] } }, + default: '', + placeholder: 'stripe.com or Stripe Inc', + description: 'Domain or company name to classify', + routing: { request: { qs: { input: '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: ['retrieveNaics', 'retrieveSic'] } }, + options: [ + { + displayName: 'Max Results', + name: 'maxResults', + type: 'number', + default: 5, + typeOptions: { minValue: 1, maxValue: 10 }, + routing: { request: { qs: { maxResults: '={{ $value }}' } } }, + }, + { + displayName: 'Min Results', + name: 'minResults', + type: 'number', + default: 1, + typeOptions: { minValue: 1 }, + routing: { request: { qs: { minResults: '={{ $value }}' } } }, + }, + { + displayName: 'SIC Dataset', + name: 'type', + type: 'options', + displayOptions: { show: { '/operation': ['retrieveSic'] } }, + default: 'original_sic', + options: [ + { name: 'Original SIC (1987)', value: 'original_sic' }, + { name: 'Latest SEC', value: 'latest_sec' }, + ], + routing: { request: { qs: { type: '={{ $value }}' } } }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + default: 30000, + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/utility/index.ts b/nodes/ContextDev/resources/utility/index.ts new file mode 100644 index 0000000..07a9939 --- /dev/null +++ b/nodes/ContextDev/resources/utility/index.ts @@ -0,0 +1,71 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['utility'] }; + +export const utilityDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show }, + options: [ + { + name: 'Prefetch by Domain', + value: 'prefetch', + action: 'Warm cache for a domain before retrieval', + description: 'Signal that you will fetch brand data for this domain soon to reduce latency', + routing: { request: { method: 'POST', url: '/brand/prefetch' } }, + }, + { + name: 'Prefetch by Email', + value: 'prefetchByEmail', + action: 'Warm cache for a domain via email address', + description: + 'Same as prefetch but accepts an email — extracts and validates the domain. Free/disposable emails are rejected.', + routing: { request: { method: 'POST', url: '/brand/prefetch-by-email' } }, + }, + ], + default: 'prefetch', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['prefetch'] } }, + default: '', + placeholder: 'stripe.com', + routing: { request: { body: { domain: '={{ $value }}' } } }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['prefetchByEmail'] } }, + default: '', + placeholder: 'contact@stripe.com', + description: + 'Free email providers (gmail.com, yahoo.com) and disposable addresses are not allowed', + routing: { request: { body: { email: '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: ['prefetch', 'prefetchByEmail'] } }, + options: [ + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + default: 30000, + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/web/brand.ts b/nodes/ContextDev/resources/web/brand.ts new file mode 100644 index 0000000..891e9fe --- /dev/null +++ b/nodes/ContextDev/resources/web/brand.ts @@ -0,0 +1,111 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['web'] }; +const visualOps = ['screenshot', 'extractStyleguide', 'extractFonts', 'extractCompetitors']; + +export const brandFields: INodeProperties[] = [ + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: visualOps } }, + default: '', + placeholder: 'stripe.com', + routing: { request: { qs: { domain: '={{ $value }}' } } }, + }, + + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: visualOps } }, + options: [ + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + displayOptions: { show: { '/operation': ['screenshot', 'extractStyleguide', 'extractFonts'] } }, + default: '', + placeholder: 'https://example.com/design-system', + description: 'Fetch from this exact URL instead of resolving from the domain', + routing: { request: { qs: { directUrl: '={{ $value }}' } } }, + }, + { + displayName: 'Full Page Screenshot', + name: 'fullScreenshot', + type: 'boolean', + displayOptions: { show: { '/operation': ['screenshot'] } }, + default: false, + description: 'Capture the full scrollable page height instead of just the visible viewport', + routing: { request: { qs: { fullScreenshot: '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Handle Cookie Popup', + name: 'handleCookiePopup', + type: 'boolean', + displayOptions: { show: { '/operation': ['screenshot'] } }, + default: false, + description: 'Attempt to dismiss cookie consent banners before taking the screenshot', + routing: { request: { qs: { handleCookiePopup: '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Max Age (Ms)', + name: 'maxAgeMs', + type: 'number', + default: 86400000, + description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', + routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, + }, + { + displayName: 'Number of Competitors', + name: 'numCompetitors', + type: 'number', + displayOptions: { show: { '/operation': ['extractCompetitors'] } }, + default: 5, + typeOptions: { minValue: 1 }, + routing: { request: { qs: { numCompetitors: '={{ $value }}' } } }, + }, + { + displayName: 'Page Type', + name: 'page', + type: 'options', + displayOptions: { show: { '/operation': ['screenshot'] } }, + default: '', + options: [ + { name: 'Blog', value: 'blog' }, + { name: 'Careers', value: 'careers' }, + { name: 'Contact', value: 'contact' }, + { name: 'Homepage (Default)', value: '' }, + { name: 'Login', value: 'login' }, + { name: 'Pricing', value: 'pricing' }, + { name: 'Privacy', value: 'privacy' }, + { name: 'Signup', value: 'signup' }, + { name: 'Terms', value: 'terms' }, + ], + routing: { request: { qs: { page: '={{ $value }}' } } }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + default: 30000, + description: 'Maximum time in milliseconds to wait for a response before the request fails.', + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, + }, + { + displayName: 'Wait For (Ms)', + name: 'waitForMs', + type: 'number', + displayOptions: { show: { '/operation': ['screenshot'] } }, + default: 0, + description: 'Delay capturing by this many milliseconds after page load. Max 30,000 ms.', + typeOptions: { minValue: 0, maxValue: 30000 }, + routing: { request: { qs: { waitForMs: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/web/index.ts b/nodes/ContextDev/resources/web/index.ts new file mode 100644 index 0000000..3c64832 --- /dev/null +++ b/nodes/ContextDev/resources/web/index.ts @@ -0,0 +1,111 @@ +import type { INodeProperties } from 'n8n-workflow'; +import { scrapingFields } from './scraping'; +import { brandFields } from './brand'; +import { searchExtractFields } from './searchExtract'; + +const show = { resource: ['web'] }; + +const operationField: INodeProperties = { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show }, + options: [ + { + name: 'Crawl Sitemap', + value: 'scrapeSitemap', + action: 'Crawl sitemap and return all discovered urls', + description: + 'Parse a domain sitemap and return all discovered page URLs. View docs.', + routing: { request: { method: 'GET', url: '/web/scrape/sitemap' } }, + }, + { + name: 'Crawl Website', + value: 'crawl', + action: 'Crawl a website and return markdown for all pages', + description: + 'Crawl multiple pages of a website and return markdown for each. View docs.', + routing: { request: { method: 'POST', url: '/web/crawl' } }, + }, + { + name: 'Extract Competitors', + value: 'extractCompetitors', + action: 'Extract competitors for a domain', + description: 'Identify direct competitors for a given brand domain.', + routing: { request: { method: 'GET', url: '/web/competitors' } }, + }, + { + name: 'Extract Fonts', + value: 'extractFonts', + action: 'Extract font information from a website', + description: + 'Extract font families, usage statistics, fallbacks, and element counts. View docs.', + routing: { request: { method: 'GET', url: '/web/fonts' } }, + }, + { + name: 'Extract Structured Data', + value: 'extract', + action: 'Extract structured data using a JSON schema', + description: + 'Crawl a website and extract data matching your JSON schema. View docs.', + routing: { request: { method: 'POST', url: '/web/extract' } }, + }, + { + name: 'Extract Styleguide', + value: 'extractStyleguide', + action: 'Extract design system from a website', + description: + 'Extract colors, typography, spacing, shadows, and UI components. View docs.', + routing: { request: { method: 'GET', url: '/web/styleguide' } }, + }, + { + name: 'Scrape HTML', + value: 'scrapeHtml', + action: 'Scrape a URL and return raw HTML', + description: + 'Scrape any URL and return the raw HTML. View docs.', + routing: { request: { method: 'GET', url: '/web/scrape/html' } }, + }, + { + name: 'Scrape Images', + value: 'scrapeImages', + action: 'Extract all images from a webpage', + description: + 'Extract image assets from a web page including URLs, inline SVGs, and metadata. View docs.', + routing: { request: { method: 'GET', url: '/web/scrape/images' } }, + }, + { + name: 'Scrape to Markdown', + value: 'scrapeMd', + action: 'Scrape a url to markdown', + description: + 'Scrape any URL and get back clean markdown content. View docs.', + routing: { request: { method: 'GET', url: '/web/scrape/markdown' } }, + }, + { + name: 'Screenshot', + value: 'screenshot', + action: 'Take a screenshot of a website', + description: + 'Capture a screenshot with options for page type and viewport. View docs.', + routing: { request: { method: 'GET', url: '/web/screenshot' } }, + }, + { + name: 'Search', + value: 'search', + action: 'Search the web and return results', + description: + 'Run a web search and get structured results with optional markdown content. View docs.', + routing: { request: { method: 'POST', url: '/web/search' } }, + }, + ], + default: 'scrapeMd', +}; + +export const webDescription: INodeProperties[] = [ + operationField, + ...scrapingFields, // scrapeMd, scrapeHtml, scrapeImages, crawl, scrapeSitemap + ...brandFields, // screenshot, extractStyleguide, extractFonts, extractCompetitors + ...searchExtractFields, // search, extract +]; diff --git a/nodes/ContextDev/resources/web/scraping.ts b/nodes/ContextDev/resources/web/scraping.ts new file mode 100644 index 0000000..d832a3c --- /dev/null +++ b/nodes/ContextDev/resources/web/scraping.ts @@ -0,0 +1,247 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['web'] }; +const scrapingOps = ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'crawl', 'scrapeSitemap']; + +export const scrapingFields: INodeProperties[] = [ + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['scrapeMd', 'scrapeHtml', 'scrapeImages'] } }, + default: '', + placeholder: 'https://stripe.com/docs', + routing: { request: { qs: { url: '={{ $value }}' } } }, + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['crawl'] } }, + default: '', + placeholder: 'https://stripe.com/blog', + routing: { request: { body: { url: '={{ $value }}' } } }, + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['scrapeSitemap'] } }, + default: '', + placeholder: 'stripe.com', + routing: { request: { qs: { domain: '={{ $value }}' } } }, + }, + + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: scrapingOps } }, + options: [ + { + displayName: 'Follow Subdomains', + name: 'followSubdomains', + type: 'boolean', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: false, + description: 'Follow links to subdomains (e.g. blog.example.com) during a crawl', + routing: { request: { body: { followSubdomains: '={{ $value }}' } } }, + }, + { + displayName: 'Include Frames', + name: 'includeFrames', + type: 'boolean', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, + default: false, + description: 'Include content from iframes embedded in the page', + routing: { request: { qs: { includeFrames: '={{ $value }}' } } }, + }, + { + displayName: 'Include Frames', + name: 'includeFramesPost', + type: 'boolean', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: false, + description: 'Include content from iframes embedded in the page', + routing: { request: { body: { includeFrames: '={{ $value }}' } } }, + }, + { + displayName: 'Include Images', + name: 'includeImages', + type: 'boolean', + displayOptions: { show: { '/operation': ['scrapeMd'] } }, + default: false, + description: 'Include image references in the returned Markdown output', + routing: { request: { qs: { includeImages: '={{ $value }}' } } }, + }, + { + displayName: 'Include Images', + name: 'includeImagesPost', + type: 'boolean', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: false, + description: 'Include image references in the returned Markdown output', + routing: { request: { body: { includeImages: '={{ $value }}' } } }, + }, + { + displayName: 'Include Links', + name: 'includeLinks', + type: 'boolean', + displayOptions: { show: { '/operation': ['scrapeMd'] } }, + default: true, + description: 'Preserve hyperlinks in the returned Markdown output', + routing: { request: { qs: { includeLinks: '={{ $value }}' } } }, + }, + { + displayName: 'Include Links', + name: 'includeLinksPost', + type: 'boolean', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: true, + description: 'Preserve hyperlinks in the returned Markdown output', + routing: { request: { body: { includeLinks: '={{ $value }}' } } }, + }, + { + displayName: 'Main Content Only', + name: 'useMainContentOnly', + type: 'boolean', + displayOptions: { show: { '/operation': ['scrapeMd'] } }, + default: false, + description: 'Strip navigation, headers, and footers — return only the main body content', + routing: { request: { qs: { useMainContentOnly: '={{ $value }}' } } }, + }, + { + displayName: 'Main Content Only', + name: 'useMainContentOnlyPost', + type: 'boolean', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: false, + description: 'Strip navigation, headers, and footers — return only the main body content', + routing: { request: { body: { useMainContentOnly: '={{ $value }}' } } }, + }, + { + displayName: 'Max Age (Ms)', + name: 'maxAgeMs', + type: 'number', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] } }, + default: 86400000, + description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', + routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, + }, + { + displayName: 'Max Age (Ms)', + name: 'maxAgeMsPost', + type: 'number', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: 86400000, + description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', + routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, + }, + { + displayName: 'Max Depth', + name: 'maxDepth', + type: 'number', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: 3, + description: 'How many link levels deep to crawl from the starting URL. 0 = starting page only.', + typeOptions: { minValue: 0 }, + routing: { request: { body: { maxDepth: '={{ $value }}' } } }, + }, + { + displayName: 'Max Links', + name: 'maxLinks', + type: 'number', + displayOptions: { show: { '/operation': ['scrapeSitemap'] } }, + default: 10000, + description: 'Maximum number of URLs to return from the sitemap. Capped at 100,000.', + typeOptions: { minValue: 1, maxValue: 100000 }, + routing: { request: { qs: { maxLinks: '={{ $value }}' } } }, + }, + { + displayName: 'Max Pages', + name: 'maxPages', + type: 'number', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: 10, + description: 'Maximum number of pages to crawl. Increase for larger sites. Hard cap: 500.', + typeOptions: { minValue: 1, maxValue: 500 }, + routing: { request: { body: { maxPages: '={{ $value }}' } } }, + }, + { + displayName: 'Stop After (Ms)', + name: 'stopAfterMs', + type: 'number', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: 120000, + description: 'Stop the crawl after this many milliseconds, returning pages collected so far. Between 10,000 and 240,000 ms.', + typeOptions: { minValue: 10000, maxValue: 240000 }, + routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] } }, + default: 30000, + description: 'Maximum time in milliseconds to wait for a response before the request fails.', + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMSPost', + type: 'number', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: 30000, + description: 'Maximum time in milliseconds to wait for a response before the request fails.', + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, + }, + { + displayName: 'URL Regex', + name: 'urlRegex', + type: 'string', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: '', + placeholder: '^https?://[^/]+/blog/', + description: 'Only crawl URLs matching this RE2 regex pattern', + routing: { request: { body: { urlRegex: '={{ $value }}' } } }, + }, + { + displayName: 'URL Regex', + name: 'urlRegexSitemap', + type: 'string', + displayOptions: { show: { '/operation': ['scrapeSitemap'] } }, + default: '', + placeholder: '^https?://[^/]+/blog/', + description: 'Only return sitemap URLs matching this RE2 regex pattern', + routing: { request: { qs: { urlRegex: '={{ $value }}' } } }, + }, + { + displayName: 'Wait For (Ms)', + name: 'waitForMs', + type: 'number', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages'] } }, + default: 0, + description: 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', + typeOptions: { minValue: 0, maxValue: 30000 }, + routing: { request: { qs: { waitForMs: '={{ $value }}' } } }, + }, + { + displayName: 'Wait For (Ms)', + name: 'waitForMsPost', + type: 'number', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: 0, + description: 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', + typeOptions: { minValue: 0, maxValue: 30000 }, + routing: { request: { body: { waitForMs: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/web/searchExtract.ts b/nodes/ContextDev/resources/web/searchExtract.ts new file mode 100644 index 0000000..e517300 --- /dev/null +++ b/nodes/ContextDev/resources/web/searchExtract.ts @@ -0,0 +1,175 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['web'] }; +const searchExtractOps = ['search', 'extract']; + +export const searchExtractFields: INodeProperties[] = [ + { + displayName: 'Query', + name: 'query', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['search'] } }, + default: '', + placeholder: 'best CRM software 2025', + routing: { request: { body: { query: '={{ $value }}' } } }, + }, + { + displayName: 'Schema (JSON)', + name: 'schema', + type: 'json', + required: true, + displayOptions: { show: { ...show, operation: ['extract'] } }, + default: '{}', + description: 'JSON Schema describing the shape of data to extract from the website', + routing: { request: { body: { schema: '={{ JSON.parse($value) }}' } } }, + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + required: true, + displayOptions: { show: { ...show, operation: ['extract'] } }, + default: '', + placeholder: 'https://stripe.com', + routing: { request: { body: { url: '={{ $value }}' } } }, + }, + + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: searchExtractOps } }, + options: [ + { + displayName: 'Exclude Domains', + name: 'excludeDomains', + type: 'string', + displayOptions: { show: { '/operation': ['search'] } }, + default: '', + placeholder: 'pinterest.com,reddit.com', + description: 'Comma-separated blocklist of domains to exclude from results', + routing: { request: { body: { excludeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' } } }, + }, + { + displayName: 'Fact Check', + name: 'factCheck', + type: 'boolean', + displayOptions: { show: { '/operation': ['extract'] } }, + default: false, + description: 'Require every returned value to be grounded in facts stated on the page', + routing: { request: { body: { factCheck: '={{ $value }}' } } }, + }, + { + displayName: 'Follow Subdomains', + name: 'followSubdomains', + type: 'boolean', + displayOptions: { show: { '/operation': ['extract'] } }, + default: false, + description: 'Follow links to subdomains during the extraction crawl', + routing: { request: { body: { followSubdomains: '={{ $value }}' } } }, + }, + { + displayName: 'Freshness', + name: 'freshness', + type: 'options', + displayOptions: { show: { '/operation': ['search'] } }, + default: '', + options: [ + { name: 'Any Time', value: '' }, + { name: 'Last 24 Hours', value: 'last_24_hours' }, + { name: 'Last Month', value: 'last_month' }, + { name: 'Last Week', value: 'last_week' }, + { name: 'Last Year', value: 'last_year' }, + ], + routing: { request: { body: { freshness: '={{ $value }}' } } }, + }, + { + displayName: 'Include Domains', + name: 'includeDomains', + type: 'string', + displayOptions: { show: { '/operation': ['search'] } }, + default: '', + placeholder: 'arxiv.org,github.com', + description: 'Comma-separated allowlist — only return results from these domains', + routing: { request: { body: { includeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' } } }, + }, + { + displayName: 'Include Frames', + name: 'includeFrames', + type: 'boolean', + displayOptions: { show: { '/operation': ['extract'] } }, + default: false, + description: 'Include content from iframes embedded in the page', + routing: { request: { body: { includeFrames: '={{ $value }}' } } }, + }, + { + displayName: 'Instructions', + name: 'instructions', + type: 'string', + displayOptions: { show: { '/operation': ['extract'] } }, + default: '', + description: 'Optional guidance on which facts to prioritize or how to interpret schema fields', + routing: { request: { body: { instructions: '={{ $value }}' } } }, + }, + { + displayName: 'Max Age (Ms)', + name: 'maxAgeMs', + type: 'number', + displayOptions: { show: { '/operation': ['extract'] } }, + default: 86400000, + description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', + routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, + }, + { + displayName: 'Query Fanout', + name: 'queryFanout', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'] } }, + default: false, + description: 'Expand the query into multiple parallel variants for broader recall', + routing: { request: { body: { queryFanout: '={{ $value }}' } } }, + }, + { + displayName: 'Scrape Results to Markdown', + name: 'markdownEnabled', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'] } }, + default: false, + description: 'Scrape each search result page and include the full content as Markdown. Significantly increases response time.', + routing: { send: { type: 'body', property: 'markdownOptions.enabled' } }, + }, + { + displayName: 'Stop After (Ms)', + name: 'stopAfterMs', + type: 'number', + displayOptions: { show: { '/operation': ['extract'] } }, + default: 120000, + description: 'Stop the extraction crawl after this many milliseconds, returning data collected so far.', + typeOptions: { minValue: 10000, maxValue: 240000 }, + routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, + }, + { + displayName: 'Timeout (Ms)', + name: 'timeoutMS', + type: 'number', + default: 30000, + description: 'Maximum time in milliseconds to wait for a response before the request fails.', + typeOptions: { minValue: 1, maxValue: 300000 }, + routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, + }, + { + displayName: 'Wait For (Ms)', + name: 'waitForMs', + type: 'number', + displayOptions: { show: { '/operation': ['extract'] } }, + default: 0, + description: 'Delay scraping by this many milliseconds after page load. Max 30,000 ms.', + typeOptions: { minValue: 0, maxValue: 30000 }, + routing: { request: { body: { waitForMs: '={{ $value }}' } } }, + }, + ], + }, +]; diff --git a/package-lock.json b/package-lock.json index 22374b7..d05cd3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3864 +1,6303 @@ { - "name": "n8n-nodes-branddev", - "version": "1.3.0", + "name": "n8n-nodes-context-dev", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "n8n-nodes-branddev", - "version": "1.3.0", + "name": "n8n-nodes-context-dev", + "version": "2.0.0", "license": "MIT", "devDependencies": { "@n8n/node-cli": "*", + "@types/jest": "^29.5.0", "eslint": "9.32.0", + "jest": "^29.5.0", "prettier": "3.6.2", "release-it": "^19.0.4", + "ts-jest": "^29.1.0", "typescript": "5.9.2" }, "peerDependencies": { "n8n-workflow": "*" } }, - "node_modules/@clack/core": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", - "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@clack/prompts": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", - "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", + "node_modules/@babel/code-frame/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "0.5.0", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } + "license": "ISC" }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "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==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "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==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@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/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "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==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "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": ">=6.9.0" } }, - "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==", + "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==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/helper-compilation-targets/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==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "yallist": "^3.0.2" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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==", "dev": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "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==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "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==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=6.9.0" } }, - "node_modules/@eslint/js": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", - "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "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==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": ">=6.9.0" } }, - "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==", + "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==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=18.18.0" + "node": ">=6.0.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.18.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=10.10.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "*" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-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==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=12.22" + "node": ">=6.9.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/core/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/core/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@clack/core": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", + "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" } }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "node_modules/@clack/prompts": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", + "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@clack/core": "0.5.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" } }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "url": "https://opencollective.com/eslint" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "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==", + "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==", "dev": true, "license": "MIT", "engines": { - "node": "20 || >=22" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@isaacs/balanced-match": "^4.0.1" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": "20 || >=22" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/@isaacs/cliui/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==", + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@n8n_io/riot-tmpl": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@n8n_io/riot-tmpl/-/riot-tmpl-4.0.1.tgz", - "integrity": "sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "eslint-config-riot": "^1.0.0" - } - }, - "node_modules/@n8n/errors": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.5.0.tgz", - "integrity": "sha512-0Vk1Eb3Uor+zeF/WVnuhFgJc51wEBTZNBlVQy3mvyr3sGmW86bP1jA7wmRsd0DZbswPwN0vNOl/TmkDTEopOtQ==", - "license": "SEE LICENSE IN LICENSE.md", - "peer": true, "dependencies": { - "callsites": "3.1.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@n8n/eslint-plugin-community-nodes": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@n8n/eslint-plugin-community-nodes/-/eslint-plugin-community-nodes-0.7.0.tgz", - "integrity": "sha512-Mc4d1jTvvXXtddxqZwGCnG747osrFfDls9AHeDFywQJGMBAIT5D6NQP/7A5X/qWNZHAkATP6xsMWhl5o6pTX8w==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "SEE LICENSE IN LICENSE.md", + "license": "ISC", "dependencies": { - "@typescript-eslint/utils": "^8.35.0", - "fastest-levenshtein": "1.0.16" + "brace-expansion": "^1.1.7" }, - "peerDependencies": { - "eslint": ">= 9" + "engines": { + "node": "*" } }, - "node_modules/@n8n/node-cli": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@n8n/node-cli/-/node-cli-0.17.0.tgz", - "integrity": "sha512-/bcw5zfP4rVYnWR38X6D7juUHMmjT3MCGgGoVv/O+JAkJX7vz7CxEtw7knKJ/0msMU3UYOzy+9os2WkCennQtA==", + "node_modules/@eslint/js": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "@clack/prompts": "^0.11.0", - "@n8n/eslint-plugin-community-nodes": "0.7.0", - "@oclif/core": "^4.5.2", - "change-case": "^5.4.4", - "eslint-import-resolver-typescript": "^4.4.3", - "eslint-plugin-import-x": "^4.15.2", - "eslint-plugin-n8n-nodes-base": "1.16.3", - "fast-glob": "3.2.12", - "handlebars": "4.7.8", - "picocolors": "1.0.1", - "prettier": "3.6.2", - "prompts": "^2.4.2", - "rimraf": "6.0.1", - "ts-morph": "^26.0.0", - "typescript-eslint": "^8.35.0" - }, - "bin": { - "n8n-node": "bin/n8n-node.mjs" + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "eslint": ">= 9" + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@n8n/tournament": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.0.6.tgz", - "integrity": "sha512-UGSxYXXVuOX0yL6HTLBStKYwLIa0+JmRKiSZSCMcM2s2Wax984KWT6XIA1TR/27i7yYpDk1MY14KsTPnuEp27A==", + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "@n8n_io/riot-tmpl": "^4.0.1", - "ast-types": "^0.16.1", - "esprima-next": "^5.8.4", - "recast": "^0.22.0" + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" }, "engines": { - "node": ">=20.15", - "pnpm": ">=9.5" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">= 8" + "node": ">=18.18.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">= 8" + "node": ">=10.10.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@nodeutils/defaults-deep": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nodeutils/defaults-deep/-/defaults-deep-1.1.0.tgz", - "integrity": "sha512-gG44cwQovaOFdSR02jR9IhVRpnDP64VN6JdjYJTfNz4J4fWn7TQnmrf22nSjRqlwlxPcW8PL/L3KbJg3tdwvpg==", + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { - "lodash": "^4.15.0" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/@oclif/core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.8.0.tgz", - "integrity": "sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==", + "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==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.2", - "ansis": "^3.17.0", - "clean-stack": "^3.0.1", - "cli-spinners": "^2.9.2", - "debug": "^4.4.3", - "ejs": "^3.1.10", - "get-package-type": "^0.1.0", - "indent-string": "^4.0.0", - "is-wsl": "^2.2.0", - "lilconfig": "^3.1.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "string-width": "^4.2.3", - "supports-color": "^8", - "tinyglobby": "^0.2.14", - "widest-line": "^3.1.0", - "wordwrap": "^1.0.0", - "wrap-ansi": "^7.0.0" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=18.0.0" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20" + "node": ">=18" } }, - "node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">= 20" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@octokit/endpoint": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", - "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" + "node": ">=18" }, - "engines": { - "node": ">= 20" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", - "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^16.0.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">= 20" + "node": ">=18" }, "peerDependencies": { - "@octokit/core": ">=6" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@octokit/plugin-request-log": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", - "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "node_modules/@inquirer/core/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" + "node": ">=8" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", - "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "node_modules/@inquirer/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^16.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" + "node": ">=8" } }, - "node_modules/@octokit/request": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", - "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^11.0.2", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "fast-content-type-parse": "^3.0.0", - "universal-user-agent": "^7.0.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 20" + "node": ">=8" } }, - "node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^16.0.0" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">= 20" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@octokit/rest": { - "version": "22.0.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", - "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^7.0.6", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/plugin-request-log": "^6.0.0", - "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">= 20" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^27.0.0" + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@phun-ky/typeof": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@phun-ky/typeof/-/typeof-2.0.3.tgz", - "integrity": "sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==", + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, "license": "MIT", "engines": { - "node": "^20.9.0 || >=22.0.0", - "npm": ">=10.8.2" - }, - "funding": { - "url": "https://github.com/phun-ky/typeof?sponsor=1" + "node": ">=18" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ts-morph/common": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", - "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "dev": true, "license": "MIT", "dependencies": { - "fast-glob": "^3.3.3", - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@ts-morph/common/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=8.6.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@ts-morph/common/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "node": ">=18" }, - "engines": { - "node": "20 || >=22" + "peerDependencies": { + "@types/node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/parse-path": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", - "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", - "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/type-utils": "8.52.0", - "@typescript-eslint/utils": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.52.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { - "node": ">= 4" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", - "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", - "debug": "^4.4.3" + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", - "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.52.0", - "@typescript-eslint/types": "^8.52.0", - "debug": "^4.4.3" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", - "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", - "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "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==", "dev": true, "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.0.0" + "node": "20 || >=22" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", - "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/utils": "8.52.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "@isaacs/balanced-match": "^4.0.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", - "typescript": ">=4.8.4 <6.0.0" + "node": "20 || >=22" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", - "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">=12" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", - "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "node_modules/@isaacs/cliui/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==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.52.0", - "@typescript-eslint/tsconfig-utils": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", - "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", - "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", - "eslint-visitor-keys": "^4.2.1" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@typescript-eslint/visitor-keys/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==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=8" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "sprintf-js": "~1.0.2" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], + "node_modules/@jest/core/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], + "node_modules/@jest/core/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=14.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=0.4.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.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==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/@jest/reporters/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "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" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "type-fest": "^0.21.3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": "*" } }, - "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==", + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansis": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", - "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, "engines": { - "node": ">=14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "tslib": "^2.0.1" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { - "retry": "0.13.1" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT", - "peer": true - }, - "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==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "possible-typed-array-names": "^1.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "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==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/basic-ftp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", - "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "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==", + "dev": true, + "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==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=6.0.0" } }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "Apache-2.0" + "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "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==", - "dev": true, + "node_modules/@n8n_io/riot-tmpl": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@n8n_io/riot-tmpl/-/riot-tmpl-4.0.1.tgz", + "integrity": "sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==", "license": "MIT", + "peer": true, "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "eslint-config-riot": "^1.0.0" } }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", + "node_modules/@n8n/errors": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@n8n/errors/-/errors-0.5.0.tgz", + "integrity": "sha512-0Vk1Eb3Uor+zeF/WVnuhFgJc51wEBTZNBlVQy3mvyr3sGmW86bP1jA7wmRsd0DZbswPwN0vNOl/TmkDTEopOtQ==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "callsites": "3.1.0" } }, - "node_modules/c12": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz", - "integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==", + "node_modules/@n8n/eslint-plugin-community-nodes": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@n8n/eslint-plugin-community-nodes/-/eslint-plugin-community-nodes-0.7.0.tgz", + "integrity": "sha512-Mc4d1jTvvXXtddxqZwGCnG747osrFfDls9AHeDFywQJGMBAIT5D6NQP/7A5X/qWNZHAkATP6xsMWhl5o6pTX8w==", "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "chokidar": "^5.0.0", - "confbox": "^0.2.2", - "defu": "^6.1.4", - "dotenv": "^17.2.3", - "exsolve": "^1.0.8", - "giget": "^2.0.0", - "jiti": "^2.6.1", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "perfect-debounce": "^2.0.0", - "pkg-types": "^2.3.0", - "rc9": "^2.1.2" + "@typescript-eslint/utils": "^8.35.0", + "fastest-levenshtein": "1.0.16" }, "peerDependencies": { - "magicast": "*" - }, - "peerDependenciesMeta": { - "magicast": { - "optional": true - } + "eslint": ">= 9" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "peer": true, + "node_modules/@n8n/node-cli": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@n8n/node-cli/-/node-cli-0.17.0.tgz", + "integrity": "sha512-/bcw5zfP4rVYnWR38X6D7juUHMmjT3MCGgGoVv/O+JAkJX7vz7CxEtw7knKJ/0msMU3UYOzy+9os2WkCennQtA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "@clack/prompts": "^0.11.0", + "@n8n/eslint-plugin-community-nodes": "0.7.0", + "@oclif/core": "^4.5.2", + "change-case": "^5.4.4", + "eslint-import-resolver-typescript": "^4.4.3", + "eslint-plugin-import-x": "^4.15.2", + "eslint-plugin-n8n-nodes-base": "1.16.3", + "fast-glob": "3.2.12", + "handlebars": "4.7.8", + "picocolors": "1.0.1", + "prettier": "3.6.2", + "prompts": "^2.4.2", + "rimraf": "6.0.1", + "ts-morph": "^26.0.0", + "typescript-eslint": "^8.35.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "n8n-node": "bin/n8n-node.mjs" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": ">= 9" } }, - "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", + "node_modules/@n8n/tournament": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@n8n/tournament/-/tournament-1.0.6.tgz", + "integrity": "sha512-UGSxYXXVuOX0yL6HTLBStKYwLIa0+JmRKiSZSCMcM2s2Wax984KWT6XIA1TR/27i7yYpDk1MY14KsTPnuEp27A==", + "license": "Apache-2.0", "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "@n8n_io/riot-tmpl": "^4.0.1", + "ast-types": "^0.16.1", + "esprima-next": "^5.8.4", + "recast": "^0.22.0" }, "engines": { - "node": ">= 0.4" + "node": ">=20.15", + "pnpm": ">=9.5" } }, - "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==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, "license": "MIT", - "peer": true, + "optional": true, "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" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "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": ">= 8" } }, - "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==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "node_modules/@nodeutils/defaults-deep": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@nodeutils/defaults-deep/-/defaults-deep-1.1.0.tgz", + "integrity": "sha512-gG44cwQovaOFdSR02jR9IhVRpnDP64VN6JdjYJTfNz4J4fWn7TQnmrf22nSjRqlwlxPcW8PL/L3KbJg3tdwvpg==", "dev": true, - "license": "MIT" - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" + "license": "ISC", + "dependencies": { + "lodash": "^4.15.0" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "node_modules/@oclif/core": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.8.0.tgz", + "integrity": "sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^5.0.0" + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=18.0.0" } }, - "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 20" } }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", "dependencies": { - "consola": "^3.2.3" + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" } }, - "node_modules/clean-stack": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", - "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "node_modules/@octokit/endpoint": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "4.0.0" + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 20" } }, - "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==", + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^5.0.0" + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 20" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">=12" + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=8" + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">=8" + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "node_modules/@octokit/request": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", "dev": true, - "license": "MIT" - }, - "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" + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">=7.0.0" + "node": ">= 20" } }, - "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/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "delayed-stream": "~1.0.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 20" } }, - "node_modules/comment-parser": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", - "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", "dev": true, "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 20" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "node_modules/@phun-ky/typeof": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@phun-ky/typeof/-/typeof-2.0.3.tgz", + "integrity": "sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==", "dev": true, "license": "MIT", "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": "^20.9.0 || >=22.0.0", + "npm": ">=10.8.2" + }, + "funding": { + "url": "https://github.com/phun-ky/typeof?sponsor=1" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "type-detect": "4.0.8" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" + "dependencies": { + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 14" + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@ts-morph/common/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8.6.0" } }, - "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==", + "node_modules/@ts-morph/common/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } }, - "node_modules/default-browser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", - "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "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==", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", - "peer": true, "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" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@babel/types": "^7.0.0" } }, - "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==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", - "peer": true, "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" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, - "license": "MIT" - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" + "@babel/types": "^7.28.2" } }, - "node_modules/degenerator/node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "@types/node": "*" } }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, "license": "MIT" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" + "@types/istanbul-lib-report": "*" } }, - "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "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", - "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "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==", "dev": true, "license": "MIT" }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" + "undici-types": ">=7.24.0 <7.24.7" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/@types/parse-path": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", + "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", + "dev": true, "license": "MIT" }, - "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", - "peer": true, - "engines": { - "node": ">= 0.4" - } + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", + "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "es-errors": "^1.3.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/type-utils": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.52.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=6" + "node": ">= 4" } }, - "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==", + "node_modules/@typescript-eslint/parser": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", + "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3" + }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", + "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "@typescript-eslint/tsconfig-utils": "^8.52.0", + "@typescript-eslint/types": "^8.52.0", + "debug": "^4.4.3" }, "engines": { - "node": ">=6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "optionalDependencies": { - "source-map": "~0.6.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/eslint": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", - "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", + "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.32.0", - "@eslint/plugin-kit": "^0.3.4", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "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.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0" }, - "bin": { - "eslint": "bin/eslint.js" + "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.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", + "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://eslint.org/donate" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/eslint-config-riot": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-riot/-/eslint-config-riot-1.0.0.tgz", - "integrity": "sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==", - "license": "MIT", - "peer": true - }, - "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==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", + "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", "dev": true, "license": "MIT", "dependencies": { - "get-tsconfig": "^4.10.1", - "stable-hash-x": "^0.2.0" + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint-import-context" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "unrs-resolver": "^1.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", + "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "unrs-resolver": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", - "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", + "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", "dev": true, - "license": "ISC", + "license": "MIT", "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" + "@typescript-eslint/project-service": "8.52.0", + "@typescript-eslint/tsconfig-utils": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": "^16.17.0 || >=18.6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/eslint-plugin-import-x": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", - "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", + "node_modules/@typescript-eslint/utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", + "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.35.0", - "comment-parser": "^1.4.1", - "debug": "^4.4.1", - "eslint-import-context": "^0.1.9", - "is-glob": "^4.0.3", - "minimatch": "^9.0.3 || ^10.0.1", - "semver": "^7.7.2", - "stable-hash-x": "^0.2.0", - "unrs-resolver": "^1.9.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint-plugin-import-x" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/utils": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0", - "eslint-import-resolver-node": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/utils": { - "optional": true - }, - "eslint-import-resolver-node": { - "optional": true - } + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/eslint-plugin-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-local/-/eslint-plugin-local-1.0.0.tgz", - "integrity": "sha512-bcwcQnKL/Iw5Vi/F2lG1he5oKD2OGjhsLmrcctkWrWq5TujgiaYb0cj3pZgr3XI54inNVnneOFdAx1daLoYLJQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-n8n-nodes-base": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.16.3.tgz", - "integrity": "sha512-edLX42Vg4B+y0kzkitTVDmHZQrG5/wUZO874N5Z9leBuxt5TG1pqMY4zdr35RlpM4p4REr/T9x+6DpsQSL63WA==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", + "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^6.21.0", - "camel-case": "^4.1.2", - "eslint-plugin-local": "^1.0.0", - "indefinite": "^2.5.1", - "pascal-case": "^3.1.2", - "pluralize": "^8.0.0", - "sentence-case": "^3.0.4", - "title-case": "^3.0.3" + "@typescript-eslint/types": "8.52.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=20.15", - "pnpm": ">=9.6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@typescript-eslint/visitor-keys/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==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "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/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "peer": true + }, + "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", + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", + "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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==", + "dev": true, + "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", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c12": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz", + "integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^17.2.3", + "exsolve": "^1.0.8", + "giget": "^2.0.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "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", + "peer": true, + "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", + "peer": true, + "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/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "peer": true, + "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-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "peer": true, + "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/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.368", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", + "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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", + "peer": true, + "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-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "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.2", + "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-config-riot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-riot/-/eslint-config-riot-1.0.0.tgz", + "integrity": "sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==", + "license": "MIT", + "peer": true + }, + "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==", + "dev": true, + "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-typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "dev": true, + "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-plugin-import-x": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.35.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.0.1", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "eslint-import-resolver-node": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-local/-/eslint-plugin-local-1.0.0.tgz", + "integrity": "sha512-bcwcQnKL/Iw5Vi/F2lG1he5oKD2OGjhsLmrcctkWrWq5TujgiaYb0cj3pZgr3XI54inNVnneOFdAx1daLoYLJQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-n8n-nodes-base": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n8n-nodes-base/-/eslint-plugin-n8n-nodes-base-1.16.3.tgz", + "integrity": "sha512-edLX42Vg4B+y0kzkitTVDmHZQrG5/wUZO874N5Z9leBuxt5TG1pqMY4zdr35RlpM4p4REr/T9x+6DpsQSL63WA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^6.21.0", + "camel-case": "^4.1.2", + "eslint-plugin-local": "^1.0.0", + "indefinite": "^2.5.1", + "pascal-case": "^3.1.2", + "pluralize": "^8.0.0", + "sentence-case": "^3.0.4", + "title-case": "^3.0.3" + }, + "engines": { + "node": ">=20.15", + "pnpm": ">=9.6" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/eslint-plugin-n8n-nodes-base/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "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==", + "dev": true, + "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": "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/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==", + "dev": true, + "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/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "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/espree/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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima-next": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/esprima-next/-/esprima-next-5.8.4.tgz", + "integrity": "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg==", + "license": "BSD-2-Clause", + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.5.0.tgz", + "integrity": "sha512-qifAYjuW5AM1eEEIsFnOwB+TGqu6ynU3OKj9WbUTOtUBHFPZqL03XUW34kbp3zm19Ald+U8dEyRXaVsUck+Y1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { - "brace-expansion": "^1.1.7" + "is-glob": "^4.0.1" }, "engines": { - "node": "*" + "node": ">= 6" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 4.9.1" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "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==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=16.0.0" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=10" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "to-regex-range": "^5.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "node": ">=8" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "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==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, + "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", "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "is-callable": "^1.2.7" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "license": "ISC", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=14" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "license": "MIT", "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "license": "ISC", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.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/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", "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "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==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, + "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", "peer": true, "dependencies": { - "flat-cache": "^3.0.4" + "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": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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", "peer": true, "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=16" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" }, "engines": { - "node": "*" + "node": ">= 14" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "giget": "dist/cli.mjs" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/git-up": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", + "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^9.2.0" + } + }, + "node_modules/git-url-parse": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", + "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "git-up": "^8.1.0" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", "dev": true, - "license": "ISC", - "peer": true, + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^7.1.3" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { - "rimraf": "bin.js" + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "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==", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "node_modules/glob/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, "engines": { - "node": ">=16" + "node": "20 || >=22" }, - "peerDependencies": { - "typescript": ">=4.2.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-n8n-nodes-base/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "peer": true, + "license": "MIT", "engines": { - "node": ">=10" + "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==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "dev": true, - "license": "Apache-2.0", + "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", + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "peer": true }, - "node_modules/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==", + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=0.4.7" }, - "funding": { - "url": "https://opencollective.com/eslint" + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", + "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", + "peer": true, "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" + "es-define-property": "^1.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/espree/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==", - "dev": true, - "license": "Apache-2.0", + "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", + "peer": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "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", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esprima-next": { - "version": "5.8.4", - "resolved": "https://registry.npmjs.org/esprima-next/-/esprima-next-5.8.4.tgz", - "integrity": "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg==", - "license": "BSD-2-Clause", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "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": ">=12" + "node": ">= 0.4" } }, - "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==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=0.10" + "node": ">= 14" } }, - "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==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=4.0" + "node": ">= 14" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "Apache-2.0", "engines": { - "node": ">=4.0" + "node": ">=16.17.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==", + "node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/eta": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-4.5.0.tgz", - "integrity": "sha512-qifAYjuW5AM1eEEIsFnOwB+TGqu6ynU3OKj9WbUTOtUBHFPZqL03XUW34kbp3zm19Ald+U8dEyRXaVsUck+Y1g==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/bgub/eta?sponsor=1" + "node": ">= 4" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=16.17" + "node": ">=6" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/execa/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } }, - "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==", + "node_modules/indefinite": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/indefinite/-/indefinite-2.5.2.tgz", + "integrity": "sha512-J3ELLIk835hmgDMUfNltTCrHz9+CteTnSuXJqvxZT18wo1U2M9/QeeJMw99QdZwPEEr1DE2aBYda101Ojjdw5g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, "engines": { - "node": ">=8.6.0" + "node": ">=8" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "once": "^1.3.0", + "wrappy": "1" } }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "node_modules/inquirer": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", + "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/prompts": "^7.10.1", + "@inquirer/type": "^3.0.10", + "mute-stream": "^2.0.0", + "run-async": "^4.0.6", + "rxjs": "^7.8.2" + }, "engines": { - "node": ">= 4.9.1" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">= 12" } }, - "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==", - "dev": true, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", + "peer": true, "dependencies": { - "flat-cache": "^4.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } + "license": "MIT" }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT", + "peer": true + }, + "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==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, + "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", + "peer": true, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "hasown": "^2.0.3" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" }, "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==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, "engines": { - "node": ">=16" + "node": ">=0.10.0" } }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "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", "peer": true, "dependencies": { - "is-callable": "^1.2.7" + "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" @@ -3867,106 +6306,116 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "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==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">= 6" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "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==", + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, "license": "MIT", - "peer": true, + "engines": { + "node": ">=12" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "license": "MIT", "peer": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "peer": true, + "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==", + "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=0.12.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "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==", + "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", "peer": true, "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", + "call-bound": "^1.0.2", "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -3975,379 +6424,353 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/is-ssh": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", + "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "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", - "peer": true, "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "protocols": "^2.0.1" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, + "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", + "peer": true, "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, "engines": { - "node": ">= 14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/giget": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "node-fetch-native": "^1.6.6", - "nypm": "^0.6.0", - "pathe": "^2.0.3" + "is-docker": "^2.0.0" }, - "bin": { - "giget": "dist/cli.mjs" + "engines": { + "node": ">=8" } }, - "node_modules/git-up": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", - "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "dependencies": { - "is-ssh": "^1.4.0", - "parse-url": "^9.2.0" - } + "license": "ISC" }, - "node_modules/git-url-parse": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", - "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", + "node_modules/issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, "license": "MIT", "dependencies": { - "git-up": "^8.1.0" + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" } }, - "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, + "license": "BSD-3-Clause", "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "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==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "is-glob": "^4.0.3" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=10.13.0" + "node": ">=10" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "BSD-3-Clause", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/istanbul-lib-report/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==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "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==", - "dev": true, - "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", - "peer": true, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "es-define-property": "^1.0.0" + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "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", - "peer": true, - "engines": { - "node": ">= 0.4" + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" } }, - "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==", + "node_modules/jake/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "has-symbols": "^1.0.3" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "function-bind": "^1.1.2" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">= 14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/jest-changed-files/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, "engines": { - "node": ">= 14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/jest-changed-files/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=16.17.0" + "node": ">=10.17.0" } }, - "node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "node_modules/jest-changed-files/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/jest-changed-files/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=6" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-changed-files/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" }, "engines": { "node": ">=6" @@ -4356,442 +6779,671 @@ "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==", + "node_modules/jest-changed-files/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } + "license": "ISC" }, - "node_modules/indefinite": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/indefinite/-/indefinite-2.5.2.tgz", - "integrity": "sha512-J3ELLIk835hmgDMUfNltTCrHz9+CteTnSuXJqvxZT18wo1U2M9/QeeJMw99QdZwPEEr1DE2aBYda101Ojjdw5g==", + "node_modules/jest-changed-files/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=6" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "peer": true - }, - "node_modules/inquirer": { - "version": "12.11.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", - "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/prompts": "^7.10.1", - "@inquirer/type": "^3.0.10", - "mute-stream": "^2.0.0", - "run-async": "^4.0.6", - "rxjs": "^7.8.2" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@types/node": ">=18" + "@types/node": "*", + "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true + }, + "ts-node": { + "optional": true } } }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 12" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "node_modules/jest-config/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT", - "peer": true + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "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==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.7.1" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" + "dependencies": { + "detect-newline": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", - "bin": { - "is-docker": "cli.js" + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, "license": "MIT", - "peer": true, "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" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "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==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-inside-container/node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", - "bin": { - "is-docker": "cli.js" + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "license": "MIT", - "peer": true, "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-ssh": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", - "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", "dependencies": { - "protocols": "^2.0.1" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-typed-array": { + "node_modules/jest-runtime/node_modules/brace-expansion": { "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "which-typed-array": "^1.1.16" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "*" } }, - "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==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/issue-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", - "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.17 || >=20.6.1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "license": "BlueOak-1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": "20 || >=22" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jake/node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, "node_modules/jiti": { "version": "2.6.1", @@ -4820,6 +7472,13 @@ "license": "BSD-3-Clause", "peer": true }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -4833,6 +7492,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "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", @@ -4840,6 +7512,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "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", @@ -4854,6 +7533,19 @@ "dev": true, "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==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonrepair": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.1.tgz", @@ -4894,6 +7586,16 @@ "node": ">=6" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4921,6 +7623,13 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4971,6 +7680,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5045,6 +7761,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5314,6 +8063,33 @@ "dev": true, "license": "MIT" }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -5424,7 +8200,6 @@ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "wrappy": "1" } @@ -5598,6 +8373,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pac-proxy-agent": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", @@ -5652,6 +8437,25 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-path": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", @@ -5710,7 +8514,6 @@ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5725,6 +8528,13 @@ "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==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", @@ -5796,6 +8606,85 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pkg-types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", @@ -5854,6 +8743,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -5912,6 +8829,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5944,6 +8878,13 @@ "destr": "^2.0.3" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", @@ -6068,11 +9009,55 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.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-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -6089,8 +9074,18 @@ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" } }, "node_modules/restore-cursor": { @@ -6386,6 +9381,24 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/stable-hash-x": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", @@ -6396,6 +9409,29 @@ "node": ">=12.0.0" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -6409,6 +9445,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6523,6 +9596,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -6565,6 +9648,80 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6640,6 +9797,13 @@ "tslib": "^2.0.3" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6683,6 +9847,107 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-jest": { + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.0", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-morph": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", @@ -6713,6 +9978,16 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -6788,6 +10063,13 @@ "node": ">=18.17" } }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -6830,6 +10112,44 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "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/update-browserslist-db/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/upper-case-first": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", @@ -6874,6 +10194,31 @@ "which-typed-array": "^1.1.2" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7050,8 +10395,28 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "license": "ISC", - "peer": true + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" }, "node_modules/wsl-utils": { "version": "0.1.0", @@ -7114,17 +10479,22 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", - "peer": true, "engines": { "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", diff --git a/package.json b/package.json index 2267a52..9f29590 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { - "name": "n8n-nodes-branddev", - "version": "1.3.0", - "description": "n8n node for Brand.dev API - retrieve brand data, screenshots, and company information", + "name": "n8n-nodes-context-dev", + "version": "2.0.0", + "description": "n8n community node for Context.dev API — brand intelligence, web scraping, industry classification, and AI data extraction", "license": "MIT", - "homepage": "https://github.com/brand-dot-dev/n8n-integration#readme", + "homepage": "https://github.com/context-dot-dev/n8n-integration#readme", "keywords": [ "n8n-community-node-package" ], "author": { "name": "Yahia Bakour", - "email": "yahia@brand.dev" + "email": "yahia@context.dev" }, "repository": { "type": "git", - "url": "https://github.com/brand-dot-dev/n8n-integration.git" + "url": "https://github.com/context-dot-dev/n8n-integration.git" }, "scripts": { "build": "n8n-node build", @@ -21,10 +21,19 @@ "dev": "n8n-node dev", "lint": "n8n-node lint", "lint:fix": "n8n-node lint --fix", - "test": "npm run lint", + "test": "jest", "release": "n8n-node release", "prepublishOnly": "n8n-node prerelease" }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "testMatch": ["**/tests/**/*.test.ts"], + "testPathIgnorePatterns": ["/node_modules/"], + "moduleNameMapper": { + "^n8n-workflow$": "/node_modules/n8n-workflow" + } + }, "files": [ "dist" ], @@ -32,17 +41,21 @@ "n8nNodesApiVersion": 1, "strict": true, "credentials": [ - "dist/credentials/BranddevApi.credentials.js" + "dist/credentials/ContextdevApi.credentials.js" ], "nodes": [ - "dist/nodes/Branddev/Branddev.node.js" + "dist/nodes/ContextDev/ContextDev.node.js" ] }, "devDependencies": { "@n8n/node-cli": "*", + "@types/jest": "^29.5.0", "eslint": "9.32.0", + "jest": "^29.5.0", "prettier": "3.6.2", "release-it": "^19.0.4", + "ts-jest": "^29.1.0", + "ts-node": "^10.9.2", "typescript": "5.9.2" }, "peerDependencies": { diff --git a/tests/aiDataExtraction.test.ts b/tests/aiDataExtraction.test.ts new file mode 100644 index 0000000..ce50536 --- /dev/null +++ b/tests/aiDataExtraction.test.ts @@ -0,0 +1,212 @@ +import { aiDataExtractionDescription } from '../nodes/ContextDev/resources/aiDataExtraction'; +import { + getOperations, + urlFor, + methodFor, + getTopField, + getAllTopFields, + getAdditionalField, + usesBodyRouting, + showsForOperations, + additionalFieldShowsFor, + getFixedCollectionValues, +} from './helpers'; + +describe('aiDataExtraction resource', () => { + // ─── Operations ──────────────────────────────────────────────────────────── + + describe('operations', () => { + it('has exactly 3 operations', () => { + expect(getOperations(aiDataExtractionDescription)).toHaveLength(3); + }); + + it('has no duplicate operation values', () => { + const values = getOperations(aiDataExtractionDescription).map((o) => o.value); + expect(new Set(values).size).toBe(values.length); + }); + + it('every operation has an action defined (required for usableAsTool)', () => { + const missing = getOperations(aiDataExtractionDescription).filter((o) => !o.action); + expect(missing).toHaveLength(0); + }); + + it('operation property has noDataExpression: true', () => { + const op = aiDataExtractionDescription.find((p) => p.name === 'operation'); + expect(op?.noDataExpression).toBe(true); + }); + }); + + // ─── Routing ─────────────────────────────────────────────────────────────── + + describe('routing', () => { + const cases: [string, string, string][] = [ + ['aiQuery', 'POST', '/brand/ai/query'], + ['extractProducts', 'POST', '/brand/ai/products'], + ['extractProduct', 'POST', '/brand/ai/product'], + ]; + + test.each(cases)('%s → %s %s', (op, method, url) => { + expect(methodFor(aiDataExtractionDescription, op)).toBe(method); + expect(urlFor(aiDataExtractionDescription, op)).toBe(url); + }); + }); + + // ─── Required fields ─────────────────────────────────────────────────────── + + describe('required fields', () => { + it('domain is required for aiQuery', () => { + const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => + showsForOperations(f).includes('aiQuery'), + ); + expect(field?.required).toBe(true); + }); + + it('domain is required for extractProducts', () => { + const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => + showsForOperations(f).includes('extractProducts'), + ); + expect(field?.required).toBe(true); + }); + + it('url is required for extractProduct', () => { + const field = getTopField(aiDataExtractionDescription, 'url'); + expect(field?.required).toBe(true); + }); + + it('data_to_extract is required for aiQuery', () => { + const field = getTopField(aiDataExtractionDescription, 'data_to_extract'); + expect(field?.required).toBe(true); + }); + }); + + // ─── displayOptions scoping ──────────────────────────────────────────────── + + describe('displayOptions scoping', () => { + it('domain shows for aiQuery and extractProducts, not extractProduct', () => { + const domainField = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => + showsForOperations(f).includes('aiQuery'), + )!; + const ops = showsForOperations(domainField); + expect(ops).toContain('aiQuery'); + expect(ops).toContain('extractProducts'); + expect(ops).not.toContain('extractProduct'); + }); + + it('url shows only for extractProduct', () => { + const field = getTopField(aiDataExtractionDescription, 'url')!; + expect(showsForOperations(field)).toEqual(['extractProduct']); + }); + + it('data_to_extract shows only for aiQuery', () => { + const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; + expect(showsForOperations(field)).toEqual(['aiQuery']); + }); + }); + + // ─── Field routing ───────────────────────────────────────────────────────── + + describe('field routing — all POST, all body', () => { + it('domain uses body routing', () => { + const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => + showsForOperations(f).includes('aiQuery'), + )!; + expect(usesBodyRouting(field)).toBe(true); + }); + + it('url uses body routing', () => { + const field = getTopField(aiDataExtractionDescription, 'url')!; + expect(usesBodyRouting(field)).toBe(true); + }); + + it('data_to_extract uses body routing', () => { + const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; + expect(usesBodyRouting(field)).toBe(true); + }); + }); + + // ─── data_to_extract fixedCollection ────────────────────────────────────── + + describe('data_to_extract fixedCollection', () => { + it('is type fixedCollection', () => { + const field = getTopField(aiDataExtractionDescription, 'data_to_extract'); + expect(field?.type).toBe('fixedCollection'); + }); + + it('has multipleValues: true', () => { + const field = getTopField(aiDataExtractionDescription, 'data_to_extract'); + expect(field?.typeOptions?.multipleValues).toBe(true); + }); + + it('datapoint_type options include text, number, date, boolean, list, url', () => { + const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); + const typeField = values.find((v) => v.name === 'datapoint_type'); + expect(typeField).toBeDefined(); + const optionValues = (typeField!.options as { value: string }[]).map((o) => o.value); + expect(optionValues).toContain('text'); + expect(optionValues).toContain('number'); + expect(optionValues).toContain('date'); + expect(optionValues).toContain('boolean'); + expect(optionValues).toContain('list'); + expect(optionValues).toContain('url'); + }); + + it('datapoint_list_type field exists (for when type is list)', () => { + const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); + expect(values.find((v) => v.name === 'datapoint_list_type')).toBeDefined(); + }); + + it('has datapoint_name, datapoint_description, datapoint_example fields', () => { + const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); + const names = values.map((v) => v.name); + expect(names).toContain('datapoint_name'); + expect(names).toContain('datapoint_description'); + expect(names).toContain('datapoint_example'); + }); + }); + + // ─── additionalFields ────────────────────────────────────────────────────── + + describe('additionalFields', () => { + it('specific_pages is type multiOptions', () => { + const field = getAdditionalField(aiDataExtractionDescription, 'specific_pages'); + expect(field?.type).toBe('multiOptions'); + }); + + it('specific_pages options include home_page, pricing, about_us, blog, careers, contact_us, faq', () => { + const field = getAdditionalField(aiDataExtractionDescription, 'specific_pages')!; + const optionValues = (field.options as { value: string }[]).map((o) => o.value); + expect(optionValues).toContain('home_page'); + expect(optionValues).toContain('pricing'); + expect(optionValues).toContain('about_us'); + expect(optionValues).toContain('blog'); + expect(optionValues).toContain('careers'); + expect(optionValues).toContain('contact_us'); + expect(optionValues).toContain('faq'); + }); + + it('specific_pages is scoped to aiQuery only', () => { + const field = getAdditionalField(aiDataExtractionDescription, 'specific_pages')!; + expect(additionalFieldShowsFor(field)).toEqual(['aiQuery']); + }); + + it('maxProducts is scoped to extractProducts only', () => { + const field = getAdditionalField(aiDataExtractionDescription, 'maxProducts')!; + expect(additionalFieldShowsFor(field)).toEqual(['extractProducts']); + }); + + it('timeoutMS exists', () => { + expect(getAdditionalField(aiDataExtractionDescription, 'timeoutMS')).toBeDefined(); + }); + }); + + // ─── All fields have defaults ────────────────────────────────────────────── + + describe('all fields have a default value', () => { + it('no top-level field is missing default', () => { + const missing = aiDataExtractionDescription + .filter((p) => p.name !== 'operation') + .filter((p) => p.default === undefined); + expect(missing.map((p) => p.name)).toEqual([]); + }); + }); +}); diff --git a/tests/brand.test.ts b/tests/brand.test.ts new file mode 100644 index 0000000..ba0b1fe --- /dev/null +++ b/tests/brand.test.ts @@ -0,0 +1,247 @@ +import { brandDescription } from '../nodes/ContextDev/resources/brand'; +import { + getOperations, + urlFor, + methodFor, + getTopField, + getAllTopFields, + getAdditionalField, + usesQsRouting, + showsForOperations, + additionalFieldShowsFor, + qsKeyFor, +} from './helpers'; + +describe('brand resource', () => { + // ─── Operations ──────────────────────────────────────────────────────────── + + describe('operations', () => { + it('has exactly 7 operations', () => { + expect(getOperations(brandDescription)).toHaveLength(7); + }); + + it('has no duplicate operation values', () => { + const values = getOperations(brandDescription).map((o) => o.value); + expect(new Set(values).size).toBe(values.length); + }); + + it('every operation has an action defined (required for usableAsTool)', () => { + const missing = getOperations(brandDescription).filter((o) => !o.action); + expect(missing).toHaveLength(0); + }); + + it('operation property has noDataExpression: true', () => { + const op = brandDescription.find((p) => p.name === 'operation'); + expect(op?.noDataExpression).toBe(true); + }); + }); + + // ─── Routing ─────────────────────────────────────────────────────────────── + + describe('routing', () => { + const cases: [string, string, string][] = [ + ['retrieve', 'GET', '/brand/retrieve'], + ['retrieveByEmail', 'GET', '/brand/retrieve-by-email'], + ['retrieveByName', 'GET', '/brand/retrieve-by-name'], + ['retrieveByTicker', 'GET', '/brand/retrieve-by-ticker'], + ['retrieveByIsin', 'GET', '/brand/retrieve-by-isin'], + ['retrieveSimplified', 'GET', '/brand/retrieve-simplified'], + ['identifyFromTransaction', 'GET', '/brand/transaction_identifier'], + ]; + + test.each(cases)('%s → %s %s', (op, method, url) => { + expect(methodFor(brandDescription, op)).toBe(method); + expect(urlFor(brandDescription, op)).toBe(url); + }); + }); + + // ─── Required fields ─────────────────────────────────────────────────────── + + describe('required fields exist and are marked required', () => { + const cases: [string][] = [ + ['domain'], + ['email'], + ['name'], + ['ticker'], + ['isin'], + ['transaction_info'], + ]; + + test.each(cases)('%s is required', (fieldName) => { + const field = getTopField(brandDescription, fieldName); + expect(field).toBeDefined(); + expect(field?.required).toBe(true); + }); + }); + + // ─── displayOptions scoping ──────────────────────────────────────────────── + + describe('displayOptions scoping — fields show for correct operations', () => { + it('domain shows for retrieve and retrieveSimplified only', () => { + const fields = getAllTopFields(brandDescription, 'domain'); + // Could be one field covering both, or two separate fields + const allOps = fields.flatMap((f) => showsForOperations(f)); + expect(allOps).toContain('retrieve'); + expect(allOps).toContain('retrieveSimplified'); + expect(allOps).not.toContain('retrieveByEmail'); + expect(allOps).not.toContain('retrieveByTicker'); + expect(allOps).not.toContain('identifyFromTransaction'); + }); + + it('email shows only for retrieveByEmail', () => { + const field = getTopField(brandDescription, 'email')!; + expect(showsForOperations(field)).toEqual(['retrieveByEmail']); + }); + + it('name shows only for retrieveByName', () => { + const field = getTopField(brandDescription, 'name')!; + expect(showsForOperations(field)).toEqual(['retrieveByName']); + }); + + it('ticker shows only for retrieveByTicker', () => { + const field = getTopField(brandDescription, 'ticker')!; + expect(showsForOperations(field)).toEqual(['retrieveByTicker']); + }); + + it('isin shows only for retrieveByIsin', () => { + const field = getTopField(brandDescription, 'isin')!; + expect(showsForOperations(field)).toEqual(['retrieveByIsin']); + }); + + it('transaction_info shows only for identifyFromTransaction', () => { + const field = getTopField(brandDescription, 'transaction_info')!; + expect(showsForOperations(field)).toEqual(['identifyFromTransaction']); + }); + }); + + // ─── Field routing ───────────────────────────────────────────────────────── + + describe('all required fields use qs routing (GET params)', () => { + const cases = ['domain', 'email', 'name', 'ticker', 'isin', 'transaction_info']; + + test.each(cases)('%s uses qs routing', (fieldName) => { + const field = getTopField(brandDescription, fieldName)!; + expect(usesQsRouting(field)).toBe(true); + }); + }); + + // ─── Field types ─────────────────────────────────────────────────────────── + + describe('field types', () => { + it('domain is type string', () => { + expect(getTopField(brandDescription, 'domain')?.type).toBe('string'); + }); + + it('email is type string', () => { + expect(getTopField(brandDescription, 'email')?.type).toBe('string'); + }); + + it('ticker is type string', () => { + expect(getTopField(brandDescription, 'ticker')?.type).toBe('string'); + }); + + it('timeoutMS in additionalFields is type number', () => { + expect(getAdditionalField(brandDescription, 'timeoutMS')?.type).toBe('number'); + }); + + it('maxSpeed in additionalFields is type boolean', () => { + expect(getAdditionalField(brandDescription, 'maxSpeed')?.type).toBe('boolean'); + }); + + it('maxAgeMs in additionalFields is type number', () => { + expect(getAdditionalField(brandDescription, 'maxAgeMs')?.type).toBe('number'); + }); + }); + + // ─── additionalFields ────────────────────────────────────────────────────── + + describe('additionalFields', () => { + it('contains maxSpeed (not old speed_optimized)', () => { + expect(getAdditionalField(brandDescription, 'maxSpeed')).toBeDefined(); + expect(getAdditionalField(brandDescription, 'speed_optimized')).toBeUndefined(); + }); + + it('contains maxAgeMs', () => { + expect(getAdditionalField(brandDescription, 'maxAgeMs')).toBeDefined(); + }); + + it('contains timeoutMS', () => { + expect(getAdditionalField(brandDescription, 'timeoutMS')).toBeDefined(); + }); + + it('contains force_language', () => { + expect(getAdditionalField(brandDescription, 'force_language')).toBeDefined(); + }); + + it('transaction-only fields (mcc, city, country_gl) have /operation scoped to identifyFromTransaction', () => { + const txFields = ['mcc', 'city', 'country_gl']; + for (const name of txFields) { + const field = getAdditionalField(brandDescription, name); + expect(field).toBeDefined(); + expect(additionalFieldShowsFor(field!)).toEqual(['identifyFromTransaction']); + } + }); + + it('ticker_exchange is scoped to retrieveByTicker only', () => { + const field = getAdditionalField(brandDescription, 'ticker_exchange'); + expect(field).toBeDefined(); + expect(additionalFieldShowsFor(field!)).toEqual(['retrieveByTicker']); + }); + }); + + // ─── Routing key names match API param names ────────────────────────────── + + describe('routing qs key names match API param names', () => { + it('domain field sends key "domain"', () => { + const field = getTopField(brandDescription, 'domain')!; + expect(qsKeyFor(field)).toBe('domain'); + }); + + it('email field sends key "email"', () => { + const field = getTopField(brandDescription, 'email')!; + expect(qsKeyFor(field)).toBe('email'); + }); + + it('name field sends key "name"', () => { + const field = getTopField(brandDescription, 'name')!; + expect(qsKeyFor(field)).toBe('name'); + }); + + it('ticker field sends key "ticker"', () => { + const field = getTopField(brandDescription, 'ticker')!; + expect(qsKeyFor(field)).toBe('ticker'); + }); + + it('isin field sends key "isin"', () => { + const field = getTopField(brandDescription, 'isin')!; + expect(qsKeyFor(field)).toBe('isin'); + }); + + it('transaction_info field sends key "transaction_info"', () => { + const field = getTopField(brandDescription, 'transaction_info')!; + expect(qsKeyFor(field)).toBe('transaction_info'); + }); + }); + + // ─── All fields have defaults ────────────────────────────────────────────── + + describe('all fields have a default value', () => { + it('no top-level field is missing default', () => { + const missing = brandDescription + .filter((p) => p.name !== 'operation') + .filter((p) => p.default === undefined); + expect(missing.map((p) => p.name)).toEqual([]); + }); + }); + + // ─── No brand.dev URLs ───────────────────────────────────────────────────── + + describe('no brand.dev references', () => { + it('no operation description contains brand.dev', () => { + const text = getOperations(brandDescription) + .map((o) => o.description ?? '') + .join(' '); + expect(text).not.toContain('brand.dev'); + }); + }); +}); diff --git a/tests/credentials.test.ts b/tests/credentials.test.ts new file mode 100644 index 0000000..20a9d50 --- /dev/null +++ b/tests/credentials.test.ts @@ -0,0 +1,89 @@ +import { ContextdevApi } from '../credentials/ContextdevApi.credentials'; + +describe('ContextdevApi credential', () => { + const cred = new ContextdevApi(); + + // ─── Identity ────────────────────────────────────────────────────────────── + + describe('identity', () => { + it('name is contextdevApi', () => { + expect(cred.name).toBe('contextdevApi'); + }); + + it('name is NOT branddevApi (old name)', () => { + expect(cred.name).not.toBe('branddevApi'); + }); + + it('displayName is Context.dev API', () => { + expect(cred.displayName).toBe('Context.dev API'); + }); + + it('documentationUrl points to context.dev not brand.dev', () => { + expect(cred.documentationUrl).toContain('context.dev'); + expect(cred.documentationUrl).not.toContain('brand.dev'); + }); + }); + + // ─── Properties ──────────────────────────────────────────────────────────── + + describe('properties', () => { + it('has an apiKey field', () => { + const field = cred.properties.find((p) => p.name === 'apiKey'); + expect(field).toBeDefined(); + }); + + it('apiKey is type string', () => { + const field = cred.properties.find((p) => p.name === 'apiKey')!; + expect(field.type).toBe('string'); + }); + + it('apiKey is password type (masked in UI)', () => { + const field = cred.properties.find((p) => p.name === 'apiKey')!; + expect(field.typeOptions?.password).toBe(true); + }); + + it('apiKey has a default value', () => { + const field = cred.properties.find((p) => p.name === 'apiKey')!; + expect(field.default).toBeDefined(); + }); + }); + + // ─── Authentication ──────────────────────────────────────────────────────── + + describe('authentication', () => { + it('uses generic auth type', () => { + expect(cred.authenticate?.type).toBe('generic'); + }); + + it('injects Authorization Bearer header', () => { + const headers = (cred.authenticate as { properties: { headers: Record } }) + .properties?.headers; + expect(headers?.Authorization).toBeDefined(); + expect(headers?.Authorization).toContain('Bearer'); + }); + + it('injects integration_name: n8n header', () => { + const headers = (cred.authenticate as { properties: { headers: Record } }) + .properties?.headers; + expect(headers?.integration_name).toBe('n8n'); + }); + }); + + // ─── Test request ────────────────────────────────────────────────────────── + + describe('credential test request', () => { + const testReq = (cred.test as { request: { baseURL?: string; url?: string } }).request; + + it('test baseURL points to api.context.dev', () => { + expect(testReq.baseURL).toContain('context.dev'); + }); + + it('test baseURL does NOT point to api.brand.dev', () => { + expect(testReq.baseURL).not.toContain('brand.dev'); + }); + + it('test URL is a brand retrieve endpoint', () => { + expect(testReq.url).toContain('/brand/retrieve'); + }); + }); +}); diff --git a/tests/helpers.ts b/tests/helpers.ts new file mode 100644 index 0000000..25d83eb --- /dev/null +++ b/tests/helpers.ts @@ -0,0 +1,124 @@ +import type { INodeProperties, INodePropertyOptions } from 'n8n-workflow'; + +// ─── Operation helpers ─────────────────────────────────────────────────────── + +export function getOperationProp(desc: INodeProperties[]): INodeProperties { + const op = desc.find((p) => p.name === 'operation' && p.type === 'options'); + if (!op) throw new Error('No operation property found'); + return op; +} + +export function getOperations(desc: INodeProperties[]): INodePropertyOptions[] { + return getOperationProp(desc).options as INodePropertyOptions[]; +} + +export function getOperation(desc: INodeProperties[], value: string): INodePropertyOptions { + const op = getOperations(desc).find((o) => o.value === value); + if (!op) throw new Error(`Operation '${value}' not found`); + return op; +} + +export function operationExists(desc: INodeProperties[], value: string): boolean { + return getOperations(desc).some((o) => o.value === value); +} + +export function allUrls(desc: INodeProperties[]): string[] { + return getOperations(desc) + .map((o) => o.routing?.request?.url as string) + .filter(Boolean); +} + +export function methodFor(desc: INodeProperties[], value: string): string { + return getOperation(desc, value).routing?.request?.method as string; +} + +export function urlFor(desc: INodeProperties[], value: string): string { + return getOperation(desc, value).routing?.request?.url as string; +} + +// ─── Field helpers ──────────────────────────────────────────────────────────── + +/** Top-level field by name */ +export function getTopField(desc: INodeProperties[], name: string): INodeProperties | undefined { + return desc.find((p) => p.name === name); +} + +/** Field inside any additionalFields collection (supports multiple collections) */ +export function getAdditionalField( + desc: INodeProperties[], + name: string, +): INodeProperties | undefined { + const collections = desc.filter((p) => p.name === 'additionalFields'); + for (const af of collections) { + if (!af.options) continue; + const found = (af.options as INodeProperties[]).find((p) => p.name === name); + if (found) return found; + } + return undefined; +} + +/** All top-level fields with a given name (there can be multiple for GET vs POST routing) */ +export function getAllTopFields(desc: INodeProperties[], name: string): INodeProperties[] { + return desc.filter((p) => p.name === name); +} + +export function usesQsRouting(field: INodeProperties): boolean { + return !!field.routing?.request?.qs; +} + +export function usesBodyRouting(field: INodeProperties): boolean { + return !!field.routing?.request?.body; +} + +/** Check which operations a top-level field's displayOptions.show.operation includes */ +export function showsForOperations(field: INodeProperties): string[] { + const show = field.displayOptions?.show as Record | undefined; + return show?.operation ?? []; +} + +/** Check which operations an additionalField's displayOptions.show['/operation'] includes */ +export function additionalFieldShowsFor(field: INodeProperties): string[] { + const show = field.displayOptions?.show as Record | undefined; + return show?.['/operation'] ?? []; +} + +// ─── fixedCollection helpers ────────────────────────────────────────────────── + +export function getFixedCollectionValues( + desc: INodeProperties[], + name: string, +): INodeProperties[] { + const field = desc.find((p) => p.name === name); + if (!field?.options) return []; + const group = (field.options as { values: INodeProperties[] }[])[0]; + return group?.values ?? []; +} + +// ─── Sweep helpers ──────────────────────────────────────────────────────────── + +/** Get the qs key name a field sends (e.g. { domain: '...' } → 'domain') */ +export function qsKeyFor(field: INodeProperties): string | undefined { + const qs = field.routing?.request?.qs; + if (!qs) return undefined; + return Object.keys(qs)[0]; +} + +/** Get the body key name a field sends (e.g. { domain: '...' } → 'domain') */ +export function bodyKeyFor(field: INodeProperties): string | undefined { + const body = field.routing?.request?.body; + if (!body) return undefined; + return Object.keys(body)[0]; +} + +/** Every INodeProperties in a description, including nested options */ +export function allFields(desc: INodeProperties[]): INodeProperties[] { + const result: INodeProperties[] = []; + for (const prop of desc) { + result.push(prop); + if (prop.type === 'collection' || prop.type === 'fixedCollection') { + const nested = prop.options as INodeProperties[] | undefined; + if (nested) result.push(...nested); + } + } + return result; +} diff --git a/tests/industry.test.ts b/tests/industry.test.ts new file mode 100644 index 0000000..0258c28 --- /dev/null +++ b/tests/industry.test.ts @@ -0,0 +1,147 @@ +import { industryDescription } from '../nodes/ContextDev/resources/industry'; +import { + getOperations, + urlFor, + methodFor, + allUrls, + getTopField, + getAdditionalField, + usesQsRouting, + showsForOperations, + additionalFieldShowsFor, + qsKeyFor, +} from './helpers'; + +describe('industry resource', () => { + // ─── Operations ──────────────────────────────────────────────────────────── + + describe('operations', () => { + it('has exactly 2 operations', () => { + expect(getOperations(industryDescription)).toHaveLength(2); + }); + + it('has no duplicate operation values', () => { + const values = getOperations(industryDescription).map((o) => o.value); + expect(new Set(values).size).toBe(values.length); + }); + + it('every operation has an action defined', () => { + const missing = getOperations(industryDescription).filter((o) => !o.action); + expect(missing).toHaveLength(0); + }); + + it('operation property has noDataExpression: true', () => { + const op = industryDescription.find((p) => p.name === 'operation'); + expect(op?.noDataExpression).toBe(true); + }); + }); + + // ─── Routing — correct URLs ──────────────────────────────────────────────── + + describe('routing — correct URLs', () => { + it('retrieveNaics → GET /web/naics', () => { + expect(methodFor(industryDescription, 'retrieveNaics')).toBe('GET'); + expect(urlFor(industryDescription, 'retrieveNaics')).toBe('/web/naics'); + }); + + it('retrieveSic → GET /web/sic', () => { + expect(methodFor(industryDescription, 'retrieveSic')).toBe('GET'); + expect(urlFor(industryDescription, 'retrieveSic')).toBe('/web/sic'); + }); + }); + + // ─── Migration — old wrong URLs are gone ────────────────────────────────── + + describe('migration — old wrong URLs do not exist', () => { + it('no operation routes to /brand/naics (old)', () => { + expect(allUrls(industryDescription)).not.toContain('/brand/naics'); + }); + + it('no operation routes to /brand/sic (old)', () => { + expect(allUrls(industryDescription)).not.toContain('/brand/sic'); + }); + }); + + // ─── Required fields ─────────────────────────────────────────────────────── + + describe('required fields', () => { + it('input field exists and is required', () => { + const field = getTopField(industryDescription, 'input'); + expect(field).toBeDefined(); + expect(field?.required).toBe(true); + }); + + it('input is type string', () => { + expect(getTopField(industryDescription, 'input')?.type).toBe('string'); + }); + + it('input uses qs routing', () => { + const field = getTopField(industryDescription, 'input')!; + expect(usesQsRouting(field)).toBe(true); + }); + }); + + // ─── displayOptions scoping ──────────────────────────────────────────────── + + describe('displayOptions scoping', () => { + it('input shows for both retrieveNaics and retrieveSic', () => { + const field = getTopField(industryDescription, 'input')!; + const ops = showsForOperations(field); + expect(ops).toContain('retrieveNaics'); + expect(ops).toContain('retrieveSic'); + }); + + it('SIC type param only shows for retrieveSic', () => { + const field = getAdditionalField(industryDescription, 'type')!; + expect(additionalFieldShowsFor(field)).toEqual(['retrieveSic']); + }); + }); + + // ─── additionalFields ────────────────────────────────────────────────────── + + describe('additionalFields', () => { + it('contains maxResults', () => { + expect(getAdditionalField(industryDescription, 'maxResults')).toBeDefined(); + }); + + it('maxResults is type number', () => { + expect(getAdditionalField(industryDescription, 'maxResults')?.type).toBe('number'); + }); + + it('contains minResults', () => { + expect(getAdditionalField(industryDescription, 'minResults')).toBeDefined(); + }); + + it('SIC dataset type param has original_sic and latest_sec options', () => { + const field = getAdditionalField(industryDescription, 'type')!; + expect(field).toBeDefined(); + const values = (field.options as { value: string }[]).map((o) => o.value); + expect(values).toContain('original_sic'); + expect(values).toContain('latest_sec'); + }); + + it('contains timeoutMS', () => { + expect(getAdditionalField(industryDescription, 'timeoutMS')).toBeDefined(); + }); + }); + + // ─── Routing key names ──────────────────────────────────────────────────── + + describe('routing key names match API param names', () => { + it('input field sends key "input"', () => { + const field = getTopField(industryDescription, 'input')!; + expect(qsKeyFor(field)).toBe('input'); + }); + }); + + // ─── All fields have defaults ────────────────────────────────────────────── + + describe('all fields have a default value', () => { + it('no top-level field is missing default', () => { + const missing = industryDescription + .filter((p) => p.name !== 'operation') + .filter((p) => p.default === undefined); + expect(missing.map((p) => p.name)).toEqual([]); + }); + }); +}); diff --git a/tests/node.test.ts b/tests/node.test.ts new file mode 100644 index 0000000..7050c26 --- /dev/null +++ b/tests/node.test.ts @@ -0,0 +1,280 @@ +import { NodeConnectionTypes } from 'n8n-workflow'; +import { ContextDev } from '../nodes/ContextDev/ContextDev.node'; +import nodeJson from '../nodes/ContextDev/ContextDev.node.json'; +import packageJson from '../package.json'; + +describe('ContextDev node', () => { + const node = new ContextDev(); + const desc = node.description; + + // ─── Identity ────────────────────────────────────────────────────────────── + + describe('identity', () => { + it('displayName is Context.dev', () => { + expect(desc.displayName).toBe('Context.dev'); + }); + + it('name is contextDev', () => { + expect(desc.name).toBe('contextDev'); + }); + + it('name is NOT branddev (old name)', () => { + expect(desc.name).not.toBe('branddev'); + }); + + it('version is 1', () => { + expect(desc.version).toBe(1); + }); + + it('group is [transform]', () => { + expect(desc.group).toEqual(['transform']); + }); + }); + + // ─── Defaults ───────────────────────────────────────────────────────────── + + describe('defaults', () => { + it('defaults.name is Context.dev', () => { + expect(desc.defaults.name).toBe('Context.dev'); + }); + }); + + // ─── Inputs / Outputs ───────────────────────────────────────────────────── + + describe('inputs and outputs', () => { + it('has one Main input', () => { + expect(desc.inputs).toEqual([NodeConnectionTypes.Main]); + }); + + it('has one Main output', () => { + expect(desc.outputs).toEqual([NodeConnectionTypes.Main]); + }); + }); + + // ─── subtitle ───────────────────────────────────────────────────────────── + + describe('subtitle', () => { + it('subtitle is defined', () => { + expect(desc.subtitle).toBeDefined(); + }); + + it('subtitle is a non-empty string', () => { + expect(typeof desc.subtitle).toBe('string'); + expect((desc.subtitle as string).length).toBeGreaterThan(0); + }); + + it('subtitle references $parameter["operation"] and $parameter["resource"]', () => { + expect(desc.subtitle).toContain('$parameter["operation"]'); + expect(desc.subtitle).toContain('$parameter["resource"]'); + }); + }); + + // ─── API config ──────────────────────────────────────────────────────────── + + describe('API config', () => { + it('baseURL is https://api.context.dev/v1', () => { + expect(desc.requestDefaults?.baseURL).toBe('https://api.context.dev/v1'); + }); + + it('baseURL does NOT contain brand.dev', () => { + expect(desc.requestDefaults?.baseURL).not.toContain('brand.dev'); + }); + + it('integration_name header is n8n', () => { + const headers = desc.requestDefaults?.headers as Record; + expect(headers?.integration_name).toBe('n8n'); + }); + + it('Accept header is application/json', () => { + const headers = desc.requestDefaults?.headers as Record; + expect(headers?.Accept).toBe('application/json'); + }); + + it('Content-Type header is application/json', () => { + const headers = desc.requestDefaults?.headers as Record; + expect(headers?.['Content-Type']).toBe('application/json'); + }); + }); + + // ─── Credentials ─────────────────────────────────────────────────────────── + + describe('credentials', () => { + it('credential name is contextdevApi', () => { + expect(desc.credentials?.[0].name).toBe('contextdevApi'); + }); + + it('credential name is NOT branddevApi (old name)', () => { + expect(desc.credentials?.[0].name).not.toBe('branddevApi'); + }); + + it('credential is marked required', () => { + expect(desc.credentials?.[0].required).toBe(true); + }); + }); + + // ─── Capabilities ────────────────────────────────────────────────────────── + + describe('capabilities', () => { + it('usableAsTool is true', () => { + expect(desc.usableAsTool).toBe(true); + }); + }); + + // ─── Resource dropdown ───────────────────────────────────────────────────── + + describe('resource dropdown', () => { + const resourceProp = () => desc.properties.find((p) => p.name === 'resource')!; + const resourceValues = () => + (resourceProp().options as { value: string }[]).map((o) => o.value); + + it('resource property has noDataExpression: true', () => { + expect(resourceProp().noDataExpression).toBe(true); + }); + + it('has exactly 5 resources', () => { + expect(resourceValues()).toHaveLength(5); + }); + + it('includes brand', () => expect(resourceValues()).toContain('brand')); + it('includes web', () => expect(resourceValues()).toContain('web')); + it('includes aiDataExtraction', () => expect(resourceValues()).toContain('aiDataExtraction')); + it('includes industry', () => expect(resourceValues()).toContain('industry')); + it('includes utility (new)', () => expect(resourceValues()).toContain('utility')); + + it('does NOT include screenshot (merged into web)', () => { + expect(resourceValues()).not.toContain('screenshot'); + }); + }); + + // ─── Operation dropdown defaults ────────────────────────────────────────── + + describe('operation dropdown defaults point to real operations', () => { + it('each operation dropdown default value exists in its own options', () => { + const operationProps = desc.properties.filter( + (p) => p.name === 'operation' && p.type === 'options', + ); + for (const prop of operationProps) { + const optionValues = (prop.options as { value: string }[]).map((o) => o.value); + expect(optionValues).toContain(prop.default); + } + }); + }); + + // ─── displayOptions.show.resource on every operation dropdown ───────────── + + describe('operation dropdowns are scoped to their resource', () => { + const resourceToOpDefault: Record = { + brand: 'retrieve', + web: 'scrapeMd', + aiDataExtraction: 'aiQuery', + industry: 'retrieveNaics', + utility: 'prefetch', + }; + + for (const [resource, sampleOp] of Object.entries(resourceToOpDefault)) { + it(`${resource} operation dropdown has displayOptions.show.resource: ['${resource}']`, () => { + const operationProp = desc.properties.find( + (p) => + p.name === 'operation' && + p.type === 'options' && + (p.options as { value: string }[]).some((o) => o.value === sampleOp), + ); + expect(operationProp).toBeDefined(); + const showResource = ( + operationProp!.displayOptions?.show as Record + )?.resource; + expect(showResource).toEqual([resource]); + }); + } + }); + + // ─── All 5 resource descriptions are spread in ──────────────────────────── + + describe('all resource descriptions are wired up', () => { + it('brand operations exist in node properties', () => { + const ops = desc.properties + .filter((p) => p.name === 'operation') + .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); + expect(ops).toContain('retrieve'); + }); + + it('web operations exist in node properties', () => { + const ops = desc.properties + .filter((p) => p.name === 'operation') + .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); + expect(ops).toContain('scrapeMd'); + }); + + it('aiDataExtraction operations exist in node properties', () => { + const ops = desc.properties + .filter((p) => p.name === 'operation') + .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); + expect(ops).toContain('aiQuery'); + }); + + it('industry operations exist in node properties', () => { + const ops = desc.properties + .filter((p) => p.name === 'operation') + .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); + expect(ops).toContain('retrieveNaics'); + }); + + it('utility operations exist in node properties', () => { + const ops = desc.properties + .filter((p) => p.name === 'operation') + .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); + expect(ops).toContain('prefetch'); + }); + }); + + // ─── .node.json codex ───────────────────────────────────────────────────── + + describe('.node.json codex', () => { + it('codex does not reference brand.dev', () => { + const text = JSON.stringify(nodeJson); + expect(text).not.toContain('brand.dev'); + }); + + it('codex does not reference Brand.dev (capitalized)', () => { + const text = JSON.stringify(nodeJson); + expect(text).not.toContain('Brand.dev'); + }); + + it('primaryDocumentation URL points to context.dev', () => { + const text = JSON.stringify(nodeJson); + expect(text).toContain('context.dev'); + }); + }); + + // ─── package.json manifest ──────────────────────────────────────────────── + + describe('package.json n8n manifest', () => { + it('n8nNodesApiVersion is 1', () => { + expect((packageJson as { n8n: { n8nNodesApiVersion: number } }).n8n.n8nNodesApiVersion).toBe(1); + }); + + it('credentials manifest points to ContextdevApi dist path', () => { + const creds = (packageJson as { n8n: { credentials: string[] } }).n8n.credentials; + expect(creds.some((c: string) => c.includes('ContextdevApi'))).toBe(true); + }); + + it('nodes manifest points to ContextDev dist path', () => { + const nodes = (packageJson as { n8n: { nodes: string[] } }).n8n.nodes; + expect(nodes.some((n: string) => n.includes('ContextDev'))).toBe(true); + }); + + it('credentials manifest does NOT reference Branddev (old path)', () => { + const creds = (packageJson as { n8n: { credentials: string[] } }).n8n.credentials; + expect(creds.some((c: string) => c.includes('Branddev') || c.includes('branddev'))).toBe(false); + }); + + it('nodes manifest does NOT reference Branddev (old path)', () => { + const nodes = (packageJson as { n8n: { nodes: string[] } }).n8n.nodes; + expect(nodes.some((n: string) => n.includes('Branddev') || n.includes('branddev'))).toBe(false); + }); + + it('package name is n8n-nodes-context-dev', () => { + expect(packageJson.name).toBe('n8n-nodes-context-dev'); + }); + }); +}); diff --git a/tests/utility.test.ts b/tests/utility.test.ts new file mode 100644 index 0000000..8e42f84 --- /dev/null +++ b/tests/utility.test.ts @@ -0,0 +1,150 @@ +import { utilityDescription } from '../nodes/ContextDev/resources/utility'; +import { + getOperations, + urlFor, + methodFor, + getTopField, + getAdditionalField, + usesBodyRouting, + showsForOperations, + bodyKeyFor, +} from './helpers'; + +describe('utility resource', () => { + // ─── Existence ───────────────────────────────────────────────────────────── + + it('exists as an array (new resource — not in old node)', () => { + expect(Array.isArray(utilityDescription)).toBe(true); + expect(utilityDescription.length).toBeGreaterThan(0); + }); + + // ─── Operations ──────────────────────────────────────────────────────────── + + describe('operations', () => { + it('has exactly 2 operations', () => { + expect(getOperations(utilityDescription)).toHaveLength(2); + }); + + it('has no duplicate operation values', () => { + const values = getOperations(utilityDescription).map((o) => o.value); + expect(new Set(values).size).toBe(values.length); + }); + + it('every operation has an action defined', () => { + const missing = getOperations(utilityDescription).filter((o) => !o.action); + expect(missing).toHaveLength(0); + }); + + it('operation property has noDataExpression: true', () => { + const op = utilityDescription.find((p) => p.name === 'operation'); + expect(op?.noDataExpression).toBe(true); + }); + }); + + // ─── Routing ─────────────────────────────────────────────────────────────── + + describe('routing', () => { + it('prefetch → POST /brand/prefetch', () => { + expect(methodFor(utilityDescription, 'prefetch')).toBe('POST'); + expect(urlFor(utilityDescription, 'prefetch')).toBe('/brand/prefetch'); + }); + + it('prefetchByEmail → POST /brand/prefetch-by-email', () => { + expect(methodFor(utilityDescription, 'prefetchByEmail')).toBe('POST'); + expect(urlFor(utilityDescription, 'prefetchByEmail')).toBe('/brand/prefetch-by-email'); + }); + }); + + // ─── Required fields ─────────────────────────────────────────────────────── + + describe('required fields', () => { + it('domain exists and is required', () => { + const field = getTopField(utilityDescription, 'domain'); + expect(field).toBeDefined(); + expect(field?.required).toBe(true); + }); + + it('email exists and is required', () => { + const field = getTopField(utilityDescription, 'email'); + expect(field).toBeDefined(); + expect(field?.required).toBe(true); + }); + }); + + // ─── displayOptions scoping ──────────────────────────────────────────────── + + describe('displayOptions scoping', () => { + it('domain shows only for prefetch', () => { + const field = getTopField(utilityDescription, 'domain')!; + expect(showsForOperations(field)).toEqual(['prefetch']); + }); + + it('email shows only for prefetchByEmail', () => { + const field = getTopField(utilityDescription, 'email')!; + expect(showsForOperations(field)).toEqual(['prefetchByEmail']); + }); + }); + + // ─── Field routing ───────────────────────────────────────────────────────── + + describe('field routing — POST body', () => { + it('domain uses body routing', () => { + const field = getTopField(utilityDescription, 'domain')!; + expect(usesBodyRouting(field)).toBe(true); + }); + + it('email uses body routing', () => { + const field = getTopField(utilityDescription, 'email')!; + expect(usesBodyRouting(field)).toBe(true); + }); + }); + + // ─── Field types ─────────────────────────────────────────────────────────── + + describe('field types', () => { + it('domain is type string', () => { + expect(getTopField(utilityDescription, 'domain')?.type).toBe('string'); + }); + + it('email is type string', () => { + expect(getTopField(utilityDescription, 'email')?.type).toBe('string'); + }); + + it('timeoutMS is type number', () => { + expect(getAdditionalField(utilityDescription, 'timeoutMS')?.type).toBe('number'); + }); + }); + + // ─── additionalFields ────────────────────────────────────────────────────── + + describe('additionalFields', () => { + it('contains timeoutMS', () => { + expect(getAdditionalField(utilityDescription, 'timeoutMS')).toBeDefined(); + }); + }); + + // ─── Routing key names ──────────────────────────────────────────────────── + + describe('routing key names match API param names', () => { + it('domain field sends key "domain"', () => { + const field = getTopField(utilityDescription, 'domain')!; + expect(bodyKeyFor(field)).toBe('domain'); + }); + + it('email field sends key "email"', () => { + const field = getTopField(utilityDescription, 'email')!; + expect(bodyKeyFor(field)).toBe('email'); + }); + }); + + // ─── All fields have defaults ────────────────────────────────────────────── + + describe('all fields have a default value', () => { + it('no top-level field is missing default', () => { + const missing = utilityDescription + .filter((p) => p.name !== 'operation') + .filter((p) => p.default === undefined); + expect(missing.map((p) => p.name)).toEqual([]); + }); + }); +}); diff --git a/tests/web.test.ts b/tests/web.test.ts new file mode 100644 index 0000000..1443e5a --- /dev/null +++ b/tests/web.test.ts @@ -0,0 +1,370 @@ +import { webDescription } from '../nodes/ContextDev/resources/web'; +import { + getOperations, + operationExists, + urlFor, + methodFor, + allUrls, + getTopField, + getAllTopFields, + getAdditionalField, + usesQsRouting, + usesBodyRouting, + showsForOperations, + additionalFieldShowsFor, + qsKeyFor, + bodyKeyFor, +} from './helpers'; + +describe('web resource', () => { + // ─── Operations ──────────────────────────────────────────────────────────── + + describe('operations', () => { + it('has exactly 11 operations', () => { + expect(getOperations(webDescription)).toHaveLength(11); + }); + + it('has no duplicate operation values', () => { + const values = getOperations(webDescription).map((o) => o.value); + expect(new Set(values).size).toBe(values.length); + }); + + it('every operation has an action defined (required for usableAsTool)', () => { + const missing = getOperations(webDescription).filter((o) => !o.action); + expect(missing).toHaveLength(0); + }); + + it('operation property has noDataExpression: true', () => { + const op = webDescription.find((p) => p.name === 'operation'); + expect(op?.noDataExpression).toBe(true); + }); + }); + + // ─── Routing — correct URLs and methods ─────────────────────────────────── + + describe('routing — correct URLs and methods', () => { + const cases: [string, string, string][] = [ + ['scrapeMd', 'GET', '/web/scrape/markdown'], + ['scrapeHtml', 'GET', '/web/scrape/html'], + ['scrapeImages', 'GET', '/web/scrape/images'], + ['crawl', 'POST', '/web/crawl'], + ['scrapeSitemap', 'GET', '/web/scrape/sitemap'], + ['search', 'POST', '/web/search'], + ['extract', 'POST', '/web/extract'], + ['screenshot', 'GET', '/web/screenshot'], + ['extractStyleguide', 'GET', '/web/styleguide'], + ['extractFonts', 'GET', '/web/fonts'], + ['extractCompetitors', 'GET', '/web/competitors'], + ]; + + test.each(cases)('%s → %s %s', (op, method, url) => { + expect(methodFor(webDescription, op)).toBe(method); + expect(urlFor(webDescription, op)).toBe(url); + }); + }); + + // ─── Migration — old wrong URLs are gone ────────────────────────────────── + + describe('migration — old wrong URLs do not exist', () => { + const banned = [ + '/web/scrape/md', + '/web/crawl/md', + '/web/sitemap', + '/brand/screenshot', + '/brand/styleguide', + '/brand/fonts', + '/brand/competitors', + ]; + + test.each(banned)('%s is gone', (url) => { + expect(allUrls(webDescription)).not.toContain(url); + }); + + it('scrapeHtml is GET not POST', () => { + expect(methodFor(webDescription, 'scrapeHtml')).toBe('GET'); + }); + + it('scrapeImages is GET not POST', () => { + expect(methodFor(webDescription, 'scrapeImages')).toBe('GET'); + }); + }); + + // ─── New operations ──────────────────────────────────────────────────────── + + describe('new operations exist', () => { + it('extract operation exists', () => { + expect(operationExists(webDescription, 'extract')).toBe(true); + }); + + it('extractCompetitors operation exists', () => { + expect(operationExists(webDescription, 'extractCompetitors')).toBe(true); + }); + }); + + // ─── Required fields ─────────────────────────────────────────────────────── + + describe('required fields exist and are marked required', () => { + it('url is required for GET scrape ops (scrapeMd, scrapeHtml, scrapeImages)', () => { + const getUrlFields = getAllTopFields(webDescription, 'url').filter((f) => { + const ops = showsForOperations(f); + return ops.includes('scrapeMd') && !ops.includes('crawl'); + }); + expect(getUrlFields.some((f) => f.required)).toBe(true); + }); + + it('url is required for POST crawl op', () => { + const postUrlFields = getAllTopFields(webDescription, 'url').filter((f) => + showsForOperations(f).includes('crawl'), + ); + expect(postUrlFields.some((f) => f.required)).toBe(true); + }); + + it('url is required for POST extract op', () => { + const postUrlFields = getAllTopFields(webDescription, 'url').filter((f) => + showsForOperations(f).includes('extract'), + ); + expect(postUrlFields.some((f) => f.required)).toBe(true); + }); + + it('domain is required for scrapeSitemap', () => { + const field = getAllTopFields(webDescription, 'domain').find((f) => + showsForOperations(f).includes('scrapeSitemap'), + ); + expect(field?.required).toBe(true); + }); + + it('query is required for search', () => { + const field = getTopField(webDescription, 'query'); + expect(field?.required).toBe(true); + }); + + it('schema is required for extract', () => { + const field = getTopField(webDescription, 'schema'); + expect(field?.required).toBe(true); + }); + }); + + // ─── displayOptions scoping ──────────────────────────────────────────────── + + describe('displayOptions scoping', () => { + it('GET url field does NOT show for crawl or extract (POST ops)', () => { + const getUrlField = getAllTopFields(webDescription, 'url').find( + (f) => showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), + ); + expect(getUrlField).toBeDefined(); + expect(showsForOperations(getUrlField!)).not.toContain('crawl'); + expect(showsForOperations(getUrlField!)).not.toContain('extract'); + }); + + it('POST url field does NOT show for scrapeMd or scrapeHtml (GET ops)', () => { + const postUrlField = getAllTopFields(webDescription, 'url').find((f) => + showsForOperations(f).includes('crawl'), + ); + expect(postUrlField).toBeDefined(); + expect(showsForOperations(postUrlField!)).not.toContain('scrapeMd'); + expect(showsForOperations(postUrlField!)).not.toContain('scrapeHtml'); + }); + + it('query shows only for search', () => { + const field = getTopField(webDescription, 'query')!; + expect(showsForOperations(field)).toEqual(['search']); + }); + + it('schema shows only for extract', () => { + const field = getTopField(webDescription, 'schema')!; + expect(showsForOperations(field)).toEqual(['extract']); + }); + + it('domain field covers scrapeSitemap, screenshot, extractStyleguide, extractFonts, extractCompetitors (across all domain fields)', () => { + const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => showsForOperations(f)); + expect(allOps).toContain('scrapeSitemap'); + expect(allOps).toContain('screenshot'); + expect(allOps).toContain('extractStyleguide'); + expect(allOps).toContain('extractFonts'); + expect(allOps).toContain('extractCompetitors'); + }); + + it('domain field does NOT show for scrapeMd, crawl, search, extract', () => { + const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => showsForOperations(f)); + expect(allOps).not.toContain('scrapeMd'); + expect(allOps).not.toContain('crawl'); + expect(allOps).not.toContain('search'); + expect(allOps).not.toContain('extract'); + }); + }); + + // ─── Field routing ───────────────────────────────────────────────────────── + + describe('field routing — GET params in qs, POST params in body', () => { + it('GET url field uses qs routing', () => { + const getUrlField = getAllTopFields(webDescription, 'url').find( + (f) => showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), + )!; + expect(usesQsRouting(getUrlField)).toBe(true); + }); + + it('POST url field (crawl/extract) uses body routing', () => { + const postUrlField = getAllTopFields(webDescription, 'url').find((f) => + showsForOperations(f).includes('crawl'), + )!; + expect(usesBodyRouting(postUrlField)).toBe(true); + }); + + it('query field uses body routing', () => { + const field = getTopField(webDescription, 'query')!; + expect(usesBodyRouting(field)).toBe(true); + }); + + it('schema field uses body routing', () => { + const field = getTopField(webDescription, 'schema')!; + expect(usesBodyRouting(field)).toBe(true); + }); + + it('domain field uses qs routing', () => { + const domainField = getAllTopFields(webDescription, 'domain').find((f) => + showsForOperations(f).includes('scrapeSitemap'), + )!; + expect(usesQsRouting(domainField)).toBe(true); + }); + }); + + // ─── Field types ─────────────────────────────────────────────────────────── + + describe('field types', () => { + it('schema is type json', () => { + expect(getTopField(webDescription, 'schema')?.type).toBe('json'); + }); + + it('query is type string', () => { + expect(getTopField(webDescription, 'query')?.type).toBe('string'); + }); + + it('maxPages in additionalFields is type number', () => { + expect(getAdditionalField(webDescription, 'maxPages')?.type).toBe('number'); + }); + + it('maxDepth in additionalFields is type number', () => { + expect(getAdditionalField(webDescription, 'maxDepth')?.type).toBe('number'); + }); + + it('followSubdomains in additionalFields is type boolean', () => { + expect(getAdditionalField(webDescription, 'followSubdomains')?.type).toBe('boolean'); + }); + + it('factCheck in additionalFields is type boolean', () => { + expect(getAdditionalField(webDescription, 'factCheck')?.type).toBe('boolean'); + }); + + it('handleCookiePopup in additionalFields is type boolean', () => { + expect(getAdditionalField(webDescription, 'handleCookiePopup')?.type).toBe('boolean'); + }); + }); + + // ─── additionalFields contents ───────────────────────────────────────────── + + describe('additionalFields', () => { + it('maxPages is scoped to crawl only', () => { + const field = getAdditionalField(webDescription, 'maxPages')!; + expect(additionalFieldShowsFor(field)).toEqual(['crawl']); + }); + + it('maxDepth is scoped to crawl only', () => { + const field = getAdditionalField(webDescription, 'maxDepth')!; + expect(additionalFieldShowsFor(field)).toEqual(['crawl']); + }); + + it('maxLinks is scoped to scrapeSitemap only', () => { + const field = getAdditionalField(webDescription, 'maxLinks')!; + expect(additionalFieldShowsFor(field)).toEqual(['scrapeSitemap']); + }); + + it('factCheck is scoped to extract only', () => { + const field = getAdditionalField(webDescription, 'factCheck')!; + expect(additionalFieldShowsFor(field)).toEqual(['extract']); + }); + + it('instructions is scoped to extract only', () => { + const field = getAdditionalField(webDescription, 'instructions')!; + expect(additionalFieldShowsFor(field)).toEqual(['extract']); + }); + + it('numCompetitors is scoped to extractCompetitors only', () => { + const field = getAdditionalField(webDescription, 'numCompetitors')!; + expect(additionalFieldShowsFor(field)).toEqual(['extractCompetitors']); + }); + + it('handleCookiePopup is scoped to screenshot only', () => { + const field = getAdditionalField(webDescription, 'handleCookiePopup')!; + expect(additionalFieldShowsFor(field)).toEqual(['screenshot']); + }); + + it('fullScreenshot is scoped to screenshot only', () => { + const field = getAdditionalField(webDescription, 'fullScreenshot')!; + expect(additionalFieldShowsFor(field)).toEqual(['screenshot']); + }); + + it('queryFanout is scoped to search only', () => { + const field = getAdditionalField(webDescription, 'queryFanout')!; + expect(additionalFieldShowsFor(field)).toEqual(['search']); + }); + + it('freshness is scoped to search only', () => { + const field = getAdditionalField(webDescription, 'freshness')!; + expect(additionalFieldShowsFor(field)).toEqual(['search']); + }); + + it('directUrl is scoped to screenshot, extractStyleguide, extractFonts', () => { + const field = getAdditionalField(webDescription, 'directUrl')!; + const ops = additionalFieldShowsFor(field); + expect(ops).toContain('screenshot'); + expect(ops).toContain('extractStyleguide'); + expect(ops).toContain('extractFonts'); + }); + }); + + // ─── Routing key names match API param names ────────────────────────────── + + describe('routing key names match API param names', () => { + it('GET url field sends key "url"', () => { + const getUrlField = getAllTopFields(webDescription, 'url').find( + (f) => showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), + )!; + expect(qsKeyFor(getUrlField)).toBe('url'); + }); + + it('POST url field sends key "url"', () => { + const postUrlField = getAllTopFields(webDescription, 'url').find((f) => + showsForOperations(f).includes('crawl'), + )!; + expect(bodyKeyFor(postUrlField)).toBe('url'); + }); + + it('domain field sends key "domain"', () => { + const domainField = getAllTopFields(webDescription, 'domain').find((f) => + showsForOperations(f).includes('scrapeSitemap'), + )!; + expect(qsKeyFor(domainField)).toBe('domain'); + }); + + it('query field sends key "query"', () => { + const field = getTopField(webDescription, 'query')!; + expect(bodyKeyFor(field)).toBe('query'); + }); + + it('schema field sends key "schema"', () => { + const field = getTopField(webDescription, 'schema')!; + expect(bodyKeyFor(field)).toBe('schema'); + }); + }); + + // ─── All fields have defaults ────────────────────────────────────────────── + + describe('all fields have a default value', () => { + it('no top-level field is missing default', () => { + const missing = webDescription + .filter((p) => p.name !== 'operation') + .filter((p) => p.default === undefined); + expect(missing.map((p) => p.name)).toEqual([]); + }); + }); +}); From a93ae6b7cb836000e1428d7d6122fb016ac5f2ac Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:26:12 +0530 Subject: [PATCH 02/10] =?UTF-8?q?add=20agents.md=20=E2=80=94=20navigation?= =?UTF-8?q?=20guide=20for=20agents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- agents.md | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/agents.md b/agents.md index 123dba0..3e6f254 100644 --- a/agents.md +++ b/agents.md @@ -1,2 +1,125 @@ -run `npm run dev` after installing dependencies to start the development server and test in browser +# Agent Context — n8n-nodes-context-dev +## What this repo is + +An n8n community node for the [Context.dev API](https://docs.context.dev). It exposes brand intelligence, web scraping, AI data extraction, industry classification, and utility (cache warming) operations as n8n workflow steps. + +The node is **declarative-style** — no `execute()` method. All API calls are described as `INodeProperties[]` arrays with `routing` metadata. n8n's HTTP engine handles the actual requests. + +--- + +## Directory structure + +``` +credentials/ + ContextdevApi.credentials.ts — API key credential (Bearer auth) + contextdev.svg — Credential icon + +nodes/ContextDev/ + ContextDev.node.ts — Main node: imports all resources, defines requestDefaults + ContextDev.node.json — Codex (categories, aliases, docs link) + context-dev-logo.svg — Node icon + resources/ + brand/index.ts — Brand Intelligence (7 ops, all GET /brand/*) + web/ + index.ts — Operation dropdown + barrel import + scraping.ts — URL-based ops: scrapeMd, scrapeHtml, scrapeImages, crawl, scrapeSitemap + brand.ts — Domain-based brand ops: screenshot, styleguide, fonts, competitors + searchExtract.ts — search (POST /web/search) + extract (POST /web/extract) + aiDataExtraction/index.ts — AI Query, Extract Products, Extract Single Product + industry/index.ts — Classify NAICS (GET /web/naics), Classify SIC (GET /web/sic) + utility/index.ts — Prefetch by Domain/Email (POST /brand/prefetch*) + +tests/ + helpers.ts — Shared test utilities (getOperation, urlFor, getAdditionalField, etc.) + brand.test.ts — 40+ assertions for brand resource + web.test.ts — 55+ assertions for web resource (routing, migration, scoping) + aiDataExtraction.test.ts — 30+ assertions for AI resource + industry.test.ts — 25+ assertions including migration URL checks + utility.test.ts — 20+ assertions for utility resource + node.test.ts — Node-level: subtitle, inputs/outputs, manifest, codex + credentials.test.ts — Credential structure and auth header +``` + +--- + +## Source of truth + +The **TypeScript SDK** is authoritative for endpoint URLs, HTTP methods, and parameter names: + +``` +/Users/main/Projects/n8n-contextdev/context-typescript-sdk/src/resources/ + brand.ts industry.ts ai.ts web.ts utility.ts +``` + +When in doubt about a param name (camelCase vs snake_case), endpoint path, or whether a field is GET qs vs POST body — read the SDK first. + +--- + +## Key architectural rules + +### Routing types +- **GET** operations send params via `routing: { request: { qs: { key: '={{ $value }}' } } }` +- **POST** operations send params via `routing: { request: { body: { key: '={{ $value }}' } } }` +- When a shared optional field (e.g. `timeoutMS`) applies to both GET and POST operations, it must be **duplicated** with different `name` values (e.g. `timeoutMS` and `timeoutMSPost`) — one qs, one body. Do not share routing across method types. + +### displayOptions scoping +- Every field **must** have `displayOptions.show.resource: ['']` AND `operation: [...]` to scope it correctly +- Inside `additionalFields` collections, inner fields use `displayOptions.show['/operation']` (note the leading slash) +- Multiple `additionalFields` blocks are allowed — each scoped to a different set of operations + +### n8n lint rules (enforced by `npm run lint`) +- Operation `options` array must be alphabetically sorted by `name` +- `additionalFields` items must be alphabetically sorted by `displayName` +- `action` strings must be sentence case +- Every operation option must have an `action` field (required for `usableAsTool: true`) +- No trailing period on `description` fields in operation options +- Credential classes must have an `icon` property + +### All fields need a `default` +n8n crashes on load if any `INodeProperties` is missing `default`. Every field, including inside `additionalFields`, must have `default`. + +--- + +## Adding a new operation + +1. Find the correct resource file (or create one under `nodes/ContextDev/resources/`) +2. Add the operation to the `options` array — alphabetically by `name` +3. Add required input fields scoped to the new operation +4. Add optional fields inside the `additionalFields` collection — alphabetically by `displayName` +5. Check the SDK for the exact param names and HTTP method +6. Run `npm run lint` and fix any issues +7. Add tests to the corresponding `tests/*.test.ts` file — at minimum: routing URL/method, required field existence, key name sent + +## Adding a new resource + +1. Create `nodes/ContextDev/resources//index.ts` +2. Export a `Description: INodeProperties[]` array +3. Import and spread it in `ContextDev.node.ts` +4. Add the resource option to the Resource dropdown (alphabetical order) +5. Add a `tests/.test.ts` + +--- + +## Running things + +```bash +npm install # install deps +npm test # run 246 Jest tests (no API key needed) +npm run lint # n8n ESLint rules +npm run build # compile TypeScript → dist/ +``` + +Tests are purely structural — they read `INodeProperties[]` arrays and assert on routing, scoping, and field types. No HTTP calls, no mocking, no API key required. + +--- + +## Common gotchas + +- **camelCase params**: the API uses camelCase for most params (`timeoutMS`, `maxAgeMs`, `maxSpeed`, `numCompetitors`, `queryFanout`). Don't snake_case them. +- **`timeoutMS` not `timeout_ms`**: this trips up anyone coming from the Python SDK which uses `timeoutMS` too but looks like it might be snake_case. +- **GET scrape ops are GET not POST**: `scrapeMd`, `scrapeHtml`, `scrapeImages` are all GET with params in query string. The old node had them wrong as POST. +- **NAICS/SIC live under `/web/`**: `GET /web/naics` and `GET /web/sic` — not `/brand/naics`. +- **Screenshot/Styleguide/Fonts/Competitors live under `/web/`**: all moved from `/brand/*` to `/web/*`. +- **`fixedCollection` routing**: `data_to_extract` in AI Query routes via `'={{ $value.datapoint }}'` — the `.datapoint` accesses the named group inside the fixedCollection. +- **`markdownOptions.enabled`**: uses `routing: { send: { type: 'body', property: 'markdownOptions.enabled' } }` — not a nested object literal (which wouldn't evaluate expressions). From 8a9e1fe8bf13755cce20b46b20b4898f3d610075 Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:57:23 +0530 Subject: [PATCH 03/10] remove Zapier/Make comparison from README Co-Authored-By: Claude Sonnet 4.6 --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index a84c81f..3f1568d 100644 --- a/README.md +++ b/README.md @@ -143,16 +143,6 @@ The node will automatically test the credentials by making a test request to the - The node supports n8n's **Usable as AI Tool** feature, making it compatible with AI agent workflows - Combine **Extract Competitors** + **Retrieve by Domain** in a loop to build a competitive intelligence pipeline -## Differences from Zapier / Make - -The Context.dev n8n node exposes **more operations** than the equivalent Zapier or Make integrations: - -- **Web Scraping** — Zapier/Make do not expose the full scraping suite (crawl, sitemap, markdown, HTML, images, search, structured extraction) -- **AI Data Extraction** — the AI Query and product extraction operations are n8n-exclusive -- **Utility** — prefetch operations for cache warming are available only in n8n -- **Industry Classification** — both NAICS and SIC classification are available; Make only exposes NAICS -- **Brand Intelligence** — all seven lookup methods (domain, simplified, name, email, ticker, ISIN, transaction) are available; Zapier exposes a subset - ## Migrating from n8n-nodes-branddev This package replaces `n8n-nodes-branddev`. The underlying service has migrated from Brand.dev to Context.dev. From e23b359c83b5dac71e5020f802a115e165100c8e Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:43:35 +0530 Subject: [PATCH 04/10] sync stopAfterMs bounds to current API limits (max 110s, default 80s) --- nodes/ContextDev/resources/web/scraping.ts | 6 +++--- nodes/ContextDev/resources/web/searchExtract.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nodes/ContextDev/resources/web/scraping.ts b/nodes/ContextDev/resources/web/scraping.ts index d832a3c..b4d2726 100644 --- a/nodes/ContextDev/resources/web/scraping.ts +++ b/nodes/ContextDev/resources/web/scraping.ts @@ -177,9 +177,9 @@ export const scrapingFields: INodeProperties[] = [ name: 'stopAfterMs', type: 'number', displayOptions: { show: { '/operation': ['crawl'] } }, - default: 120000, - description: 'Stop the crawl after this many milliseconds, returning pages collected so far. Between 10,000 and 240,000 ms.', - typeOptions: { minValue: 10000, maxValue: 240000 }, + default: 80000, + description: 'Stop the crawl after this many milliseconds, returning pages collected so far. Between 10,000 and 110,000 ms.', + typeOptions: { minValue: 10000, maxValue: 110000 }, routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, }, { diff --git a/nodes/ContextDev/resources/web/searchExtract.ts b/nodes/ContextDev/resources/web/searchExtract.ts index e517300..fe7594a 100644 --- a/nodes/ContextDev/resources/web/searchExtract.ts +++ b/nodes/ContextDev/resources/web/searchExtract.ts @@ -146,9 +146,9 @@ export const searchExtractFields: INodeProperties[] = [ name: 'stopAfterMs', type: 'number', displayOptions: { show: { '/operation': ['extract'] } }, - default: 120000, - description: 'Stop the extraction crawl after this many milliseconds, returning data collected so far.', - typeOptions: { minValue: 10000, maxValue: 240000 }, + default: 80000, + description: 'Stop the extraction crawl after this many milliseconds, returning data collected so far. Between 10,000 and 110,000 ms.', + typeOptions: { minValue: 10000, maxValue: 110000 }, routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, }, { From 9fa558053d41807c271f0c234a10b2cbedcf22ef Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:23:33 +0530 Subject: [PATCH 05/10] enhance scraping and searchExtract fields with additional parameters for selectors and headers --- nodes/ContextDev/resources/web/scraping.ts | 120 ++++++++++++++-- .../ContextDev/resources/web/searchExtract.ts | 45 +++++- tests/helpers.ts | 26 +++- tests/web.test.ts | 133 +++++++++++++++++- 4 files changed, 300 insertions(+), 24 deletions(-) diff --git a/nodes/ContextDev/resources/web/scraping.ts b/nodes/ContextDev/resources/web/scraping.ts index b4d2726..caeb9dd 100644 --- a/nodes/ContextDev/resources/web/scraping.ts +++ b/nodes/ContextDev/resources/web/scraping.ts @@ -110,7 +110,7 @@ export const scrapingFields: INodeProperties[] = [ displayName: 'Main Content Only', name: 'useMainContentOnly', type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd'] } }, + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, default: false, description: 'Strip navigation, headers, and footers — return only the main body content', routing: { request: { qs: { useMainContentOnly: '={{ $value }}' } } }, @@ -124,11 +124,105 @@ export const scrapingFields: INodeProperties[] = [ description: 'Strip navigation, headers, and footers — return only the main body content', routing: { request: { body: { useMainContentOnly: '={{ $value }}' } } }, }, + { + displayName: 'Include Selectors', + name: 'includeSelectors', + type: 'string', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, + default: '', + description: + 'Comma-separated CSS selectors. When set, only matching subtrees are kept (e.g. "article.main, #content").', + routing: { + request: { + qs: { includeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, + arrayFormat: 'comma', + }, + }, + }, + { + displayName: 'Include Selectors', + name: 'includeSelectorsPost', + type: 'string', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: '', + description: + 'Comma-separated CSS selectors. When set, only matching subtrees are kept (e.g. "article.main, #content").', + routing: { + request: { + body: { + includeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectors', + type: 'string', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, + default: '', + description: + 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner").', + routing: { + request: { + qs: { excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, + arrayFormat: 'comma', + }, + }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectorsPost', + type: 'string', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: '', + description: + 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner").', + routing: { + request: { + body: { + excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + }, + { + displayName: 'Custom Headers', + name: 'headers', + type: 'fixedCollection', + typeOptions: { multipleValues: true }, + default: {}, + displayOptions: { + show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, + }, + description: + 'Custom HTTP headers forwarded to the target URL. Sending headers bypasses the cache.', + options: [ + { + name: 'header', + displayName: 'Header', + values: [ + { displayName: 'Name', name: 'name', type: 'string', default: '' }, + { displayName: 'Value', name: 'value', type: 'string', default: '' }, + ], + }, + ], + routing: { + request: { + qs: { + headers: + '={{ Object.fromEntries(($value.header || []).map(h => [h.name, h.value])) }}', + }, + }, + }, + }, { displayName: 'Max Age (Ms)', name: 'maxAgeMs', type: 'number', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] } }, + displayOptions: { + show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, + }, default: 86400000, description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, @@ -148,7 +242,8 @@ export const scrapingFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['crawl'] } }, default: 3, - description: 'How many link levels deep to crawl from the starting URL. 0 = starting page only.', + description: + 'How many link levels deep to crawl from the starting URL. 0 = starting page only.', typeOptions: { minValue: 0 }, routing: { request: { body: { maxDepth: '={{ $value }}' } } }, }, @@ -178,7 +273,8 @@ export const scrapingFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['crawl'] } }, default: 80000, - description: 'Stop the crawl after this many milliseconds, returning pages collected so far. Between 10,000 and 110,000 ms.', + description: + 'Stop the crawl after this many milliseconds, returning pages collected so far. Between 10,000 and 110,000 ms.', typeOptions: { minValue: 10000, maxValue: 110000 }, routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, }, @@ -186,9 +282,12 @@ export const scrapingFields: INodeProperties[] = [ displayName: 'Timeout (Ms)', name: 'timeoutMS', type: 'number', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] } }, + displayOptions: { + show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, + }, default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails.', + description: + 'Maximum time in milliseconds to wait for a response before the request fails.', typeOptions: { minValue: 1, maxValue: 300000 }, routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, }, @@ -198,7 +297,8 @@ export const scrapingFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['crawl'] } }, default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails.', + description: + 'Maximum time in milliseconds to wait for a response before the request fails.', typeOptions: { minValue: 1, maxValue: 300000 }, routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, }, @@ -228,7 +328,8 @@ export const scrapingFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages'] } }, default: 0, - description: 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', + description: + 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', typeOptions: { minValue: 0, maxValue: 30000 }, routing: { request: { qs: { waitForMs: '={{ $value }}' } } }, }, @@ -238,7 +339,8 @@ export const scrapingFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['crawl'] } }, default: 0, - description: 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', + description: + 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', typeOptions: { minValue: 0, maxValue: 30000 }, routing: { request: { body: { waitForMs: '={{ $value }}' } } }, }, diff --git a/nodes/ContextDev/resources/web/searchExtract.ts b/nodes/ContextDev/resources/web/searchExtract.ts index fe7594a..98c15f6 100644 --- a/nodes/ContextDev/resources/web/searchExtract.ts +++ b/nodes/ContextDev/resources/web/searchExtract.ts @@ -51,7 +51,11 @@ export const searchExtractFields: INodeProperties[] = [ default: '', placeholder: 'pinterest.com,reddit.com', description: 'Comma-separated blocklist of domains to exclude from results', - routing: { request: { body: { excludeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' } } }, + routing: { + request: { + body: { excludeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, + }, + }, }, { displayName: 'Fact Check', @@ -94,7 +98,11 @@ export const searchExtractFields: INodeProperties[] = [ default: '', placeholder: 'arxiv.org,github.com', description: 'Comma-separated allowlist — only return results from these domains', - routing: { request: { body: { includeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' } } }, + routing: { + request: { + body: { includeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, + }, + }, }, { displayName: 'Include Frames', @@ -111,7 +119,8 @@ export const searchExtractFields: INodeProperties[] = [ type: 'string', displayOptions: { show: { '/operation': ['extract'] } }, default: '', - description: 'Optional guidance on which facts to prioritize or how to interpret schema fields', + description: + 'Optional guidance on which facts to prioritize or how to interpret schema fields', routing: { request: { body: { instructions: '={{ $value }}' } } }, }, { @@ -123,6 +132,27 @@ export const searchExtractFields: INodeProperties[] = [ description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, }, + { + displayName: 'Max Depth', + name: 'maxDepth', + type: 'number', + displayOptions: { show: { '/operation': ['extract'] } }, + default: 3, + description: + 'Maximum link depth to crawl from the starting URL during extraction. 0 = starting page only.', + typeOptions: { minValue: 0 }, + routing: { request: { body: { maxDepth: '={{ $value }}' } } }, + }, + { + displayName: 'Max Pages', + name: 'maxPages', + type: 'number', + displayOptions: { show: { '/operation': ['extract'] } }, + default: 5, + description: 'Maximum number of pages to analyze for extraction. Hard cap: 50.', + typeOptions: { minValue: 1, maxValue: 50 }, + routing: { request: { body: { maxPages: '={{ $value }}' } } }, + }, { displayName: 'Query Fanout', name: 'queryFanout', @@ -138,7 +168,8 @@ export const searchExtractFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['search'] } }, default: false, - description: 'Scrape each search result page and include the full content as Markdown. Significantly increases response time.', + description: + 'Scrape each search result page and include the full content as Markdown. Significantly increases response time.', routing: { send: { type: 'body', property: 'markdownOptions.enabled' } }, }, { @@ -147,7 +178,8 @@ export const searchExtractFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['extract'] } }, default: 80000, - description: 'Stop the extraction crawl after this many milliseconds, returning data collected so far. Between 10,000 and 110,000 ms.', + description: + 'Stop the extraction crawl after this many milliseconds, returning data collected so far. Between 10,000 and 110,000 ms.', typeOptions: { minValue: 10000, maxValue: 110000 }, routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, }, @@ -156,7 +188,8 @@ export const searchExtractFields: INodeProperties[] = [ name: 'timeoutMS', type: 'number', default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails.', + description: + 'Maximum time in milliseconds to wait for a response before the request fails.', typeOptions: { minValue: 1, maxValue: 300000 }, routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, }, diff --git a/tests/helpers.ts b/tests/helpers.ts index 25d83eb..b89220b 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -62,6 +62,22 @@ export function getAllTopFields(desc: INodeProperties[], name: string): INodePro return desc.filter((p) => p.name === name); } +/** additionalField with a given name scoped to a specific operation (disambiguates duplicates) */ +export function getAdditionalFieldFor( + desc: INodeProperties[], + name: string, + op: string, +): INodeProperties | undefined { + const collections = desc.filter((p) => p.name === 'additionalFields'); + for (const af of collections) { + const found = (af.options as INodeProperties[] | undefined)?.find( + (p) => p.name === name && additionalFieldShowsFor(p).includes(op), + ); + if (found) return found; + } + return undefined; +} + export function usesQsRouting(field: INodeProperties): boolean { return !!field.routing?.request?.qs; } @@ -84,10 +100,7 @@ export function additionalFieldShowsFor(field: INodeProperties): string[] { // ─── fixedCollection helpers ────────────────────────────────────────────────── -export function getFixedCollectionValues( - desc: INodeProperties[], - name: string, -): INodeProperties[] { +export function getFixedCollectionValues(desc: INodeProperties[], name: string): INodeProperties[] { const field = desc.find((p) => p.name === name); if (!field?.options) return []; const group = (field.options as { values: INodeProperties[] }[])[0]; @@ -110,6 +123,11 @@ export function bodyKeyFor(field: INodeProperties): string | undefined { return Object.keys(body)[0]; } +/** Get the arrayFormat set on a field's routing.request (controls qs array serialization) */ +export function arrayFormatFor(field: INodeProperties): string | undefined { + return (field.routing?.request as { arrayFormat?: string } | undefined)?.arrayFormat; +} + /** Every INodeProperties in a description, including nested options */ export function allFields(desc: INodeProperties[]): INodeProperties[] { const result: INodeProperties[] = []; diff --git a/tests/web.test.ts b/tests/web.test.ts index 1443e5a..7dc8c0a 100644 --- a/tests/web.test.ts +++ b/tests/web.test.ts @@ -14,6 +14,8 @@ import { additionalFieldShowsFor, qsKeyFor, bodyKeyFor, + arrayFormatFor, + getAdditionalFieldFor, } from './helpers'; describe('web resource', () => { @@ -149,7 +151,8 @@ describe('web resource', () => { describe('displayOptions scoping', () => { it('GET url field does NOT show for crawl or extract (POST ops)', () => { const getUrlField = getAllTopFields(webDescription, 'url').find( - (f) => showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), + (f) => + showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), ); expect(getUrlField).toBeDefined(); expect(showsForOperations(getUrlField!)).not.toContain('crawl'); @@ -176,7 +179,9 @@ describe('web resource', () => { }); it('domain field covers scrapeSitemap, screenshot, extractStyleguide, extractFonts, extractCompetitors (across all domain fields)', () => { - const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => showsForOperations(f)); + const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => + showsForOperations(f), + ); expect(allOps).toContain('scrapeSitemap'); expect(allOps).toContain('screenshot'); expect(allOps).toContain('extractStyleguide'); @@ -185,7 +190,9 @@ describe('web resource', () => { }); it('domain field does NOT show for scrapeMd, crawl, search, extract', () => { - const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => showsForOperations(f)); + const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => + showsForOperations(f), + ); expect(allOps).not.toContain('scrapeMd'); expect(allOps).not.toContain('crawl'); expect(allOps).not.toContain('search'); @@ -198,7 +205,8 @@ describe('web resource', () => { describe('field routing — GET params in qs, POST params in body', () => { it('GET url field uses qs routing', () => { const getUrlField = getAllTopFields(webDescription, 'url').find( - (f) => showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), + (f) => + showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), )!; expect(usesQsRouting(getUrlField)).toBe(true); }); @@ -327,7 +335,8 @@ describe('web resource', () => { describe('routing key names match API param names', () => { it('GET url field sends key "url"', () => { const getUrlField = getAllTopFields(webDescription, 'url').find( - (f) => showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), + (f) => + showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), )!; expect(qsKeyFor(getUrlField)).toBe('url'); }); @@ -367,4 +376,118 @@ describe('web resource', () => { expect(missing.map((p) => p.name)).toEqual([]); }); }); + + // ─── Selector & header params (SDK 1.34) — wire-contract guarantees ───────── + // + // The OpenAPI marks includeSelectors/excludeSelectors as style:deepObject, but the + // official Stainless SDK ignores style/explode and serializes EVERY query param via + // qs.stringify(query, { arrayFormat: 'comma' }). So the real server contract is + // comma-joined: includeSelectors: ['a','b'] -> includeSelectors=a%2Cb + // n8n uses the same qs lib, so arrayFormat:'comma' on the GET fields makes the node + // byte-identical to the SDK. On crawl (POST) the params ride in the JSON body as a + // plain array, so no qs/arrayFormat applies. These tests lock that contract. + + describe('selector & header params (SDK 1.34) — wire-contract guarantees', () => { + describe.each(['includeSelectors', 'excludeSelectors'])('%s — GET ops (qs array)', (name) => { + it('is scoped to scrapeMd + scrapeHtml', () => { + const f = getAdditionalField(webDescription, name); + expect(f).toBeDefined(); + expect(additionalFieldShowsFor(f!).sort()).toEqual(['scrapeHtml', 'scrapeMd']); + }); + + it(`routes to qs key "${name}"`, () => { + expect(qsKeyFor(getAdditionalField(webDescription, name)!)).toBe(name); + }); + + it('sets arrayFormat "comma" (byte-identical to the SDK)', () => { + expect(arrayFormatFor(getAdditionalField(webDescription, name)!)).toBe('comma'); + }); + + it('splits the input into an array before serialization', () => { + const f = getAdditionalField(webDescription, name)!; + const expr = (f.routing!.request!.qs as Record)[name]; + expect(expr).toContain('.split(",")'); + }); + }); + + describe.each(['includeSelectorsPost', 'excludeSelectorsPost'])( + '%s — crawl (JSON body array)', + (name) => { + const apiKey = name.replace('Post', ''); + + it('is scoped to crawl only', () => { + expect(additionalFieldShowsFor(getAdditionalField(webDescription, name)!)).toEqual([ + 'crawl', + ]); + }); + + it(`routes to body key "${apiKey}"`, () => { + expect(bodyKeyFor(getAdditionalField(webDescription, name)!)).toBe(apiKey); + }); + + it('does NOT set arrayFormat (body is JSON, not a query string)', () => { + expect(arrayFormatFor(getAdditionalField(webDescription, name)!)).toBeUndefined(); + }); + }, + ); + + describe('headers — deep-object map', () => { + it('is a fixedCollection scoped to the four GET scrape ops', () => { + const f = getAdditionalField(webDescription, 'headers')!; + expect(f.type).toBe('fixedCollection'); + expect(additionalFieldShowsFor(f).sort()).toEqual([ + 'scrapeHtml', + 'scrapeImages', + 'scrapeMd', + 'scrapeSitemap', + ]); + }); + + it('routes to qs key "headers"', () => { + expect(qsKeyFor(getAdditionalField(webDescription, 'headers')!)).toBe('headers'); + }); + + it('exposes Name and Value sub-fields', () => { + const f = getAdditionalField(webDescription, 'headers')!; + const values = (f.options as Array<{ values: Array<{ name: string }> }>)[0].values; + expect(values.map((v) => v.name)).toEqual(['name', 'value']); + }); + + it('builds an object via Object.fromEntries (-> headers[Key]=Value)', () => { + const f = getAdditionalField(webDescription, 'headers')!; + const expr = (f.routing!.request!.qs as Record).headers; + expect(expr).toContain('Object.fromEntries'); + }); + }); + + describe('useMainContentOnly extended to scrapeHtml (1.34)', () => { + it('GET field shows for scrapeMd + scrapeHtml', () => { + const f = getAdditionalField(webDescription, 'useMainContentOnly')!; + expect(additionalFieldShowsFor(f).sort()).toEqual(['scrapeHtml', 'scrapeMd']); + }); + }); + }); + + // ─── extract gained maxDepth + maxPages (SDK 1.34) ────────────────────────── + // These param names already existed on crawl, so they need operation-scoped + // lookup. Extract's maxPages cap is 50; crawl's is 500 — distinct bounds. + describe('extract crawl-control params (SDK 1.34)', () => { + it('maxDepth is exposed for extract and routes to body', () => { + const f = getAdditionalFieldFor(webDescription, 'maxDepth', 'extract'); + expect(f).toBeDefined(); + expect(bodyKeyFor(f!)).toBe('maxDepth'); + }); + + it('maxPages is exposed for extract, routes to body, capped at 50', () => { + const f = getAdditionalFieldFor(webDescription, 'maxPages', 'extract'); + expect(f).toBeDefined(); + expect(bodyKeyFor(f!)).toBe('maxPages'); + expect((f!.typeOptions as { maxValue?: number }).maxValue).toBe(50); + }); + + it('crawl maxPages keeps its own 500 cap (distinct bound)', () => { + const f = getAdditionalFieldFor(webDescription, 'maxPages', 'crawl'); + expect((f!.typeOptions as { maxValue?: number }).maxValue).toBe(500); + }); + }); }); From 529a9d014f4b76a47643bb7e7bcd2cabda2e6708 Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:15:56 +0530 Subject: [PATCH 06/10] Migrate to api.context.dev/v1 with full SDK v1.41 parity (v3.0.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the node into complete parity with the Context.dev TypeScript SDK (v1.41) across every resource: - New Monitors resource (11 operations): create / get / update / delete / run, list monitors, list account changes & runs, list monitor changes & runs, get change. Create models the discriminated target/change_detection union with conditional fields; update mirrors it; list ops expose cursor/limit pagination and status/tag/type/since/until filters. - Web / brand / AI / industry / utility: every request parameter, enum value and numeric bound synced to the SDK — nested pdf/viewport/enrichment and markdownOptions objects, colorScheme/country/numResults/scrollOffset, webhook.secret, AI specific_pages, full ticker_exchange (72) and force_language (120) enums, correct per-operation scoping and min/max bounds. Verified against the SDK OpenAPI spec (0 endpoint/param/enum/bound mismatches) and an independent scan. 379 tests pass; node is lint-clean. --- CHANGELOG.md | 6 + nodes/ContextDev/ContextDev.node.ts | 3 + .../resources/aiDataExtraction/index.ts | 139 +++- nodes/ContextDev/resources/brand/index.ts | 220 +++++- nodes/ContextDev/resources/industry/index.ts | 2 +- nodes/ContextDev/resources/monitors/index.ts | 737 ++++++++++++++++++ nodes/ContextDev/resources/utility/index.ts | 2 +- nodes/ContextDev/resources/web/brand.ts | 89 ++- nodes/ContextDev/resources/web/index.ts | 2 +- nodes/ContextDev/resources/web/scraping.ts | 325 +++++--- .../ContextDev/resources/web/searchExtract.ts | 185 ++++- package.json | 2 +- tests/aiDataExtraction.test.ts | 97 ++- tests/brand.test.ts | 75 +- tests/helpers.ts | 10 + tests/monitors.test.ts | 390 +++++++++ tests/node.test.ts | 5 +- tests/web.test.ts | 236 +++++- 18 files changed, 2394 insertions(+), 131 deletions(-) create mode 100644 nodes/ContextDev/resources/monitors/index.ts create mode 100644 tests/monitors.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f9959..40eb908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### 3.0.0 + +- Migrate to the api.context.dev/v1 API with full SDK v1.41 parity across every resource +- Add the **Monitors** resource — 11 operations (create / get / update / delete / run, list monitors, list account changes & runs, list monitor changes & runs, get change) +- Sync all web / brand / AI / industry / utility request params, enums and numeric bounds to the SDK — including nested pdf/viewport/enrichment/markdownOptions, discriminated monitor targets, webhook secret, AI specific_pages, and correct per-operation scoping + #### [1.3.0](https://github.com/brand-dot-dev/n8n-integration/compare/1.2.0...1.3.0) - Update repository URL in package.json [`72cc1af`](https://github.com/brand-dot-dev/n8n-integration/commit/72cc1afb0becc8a5af231d848cd4f53ff08fd65d) diff --git a/nodes/ContextDev/ContextDev.node.ts b/nodes/ContextDev/ContextDev.node.ts index ad8270c..a6840e4 100644 --- a/nodes/ContextDev/ContextDev.node.ts +++ b/nodes/ContextDev/ContextDev.node.ts @@ -5,6 +5,7 @@ import { webDescription } from './resources/web'; import { aiDataExtractionDescription } from './resources/aiDataExtraction'; import { industryDescription } from './resources/industry'; import { utilityDescription } from './resources/utility'; +import { monitorsDescription } from './resources/monitors'; export class ContextDev implements INodeType { description: INodeTypeDescription = { @@ -40,6 +41,7 @@ export class ContextDev implements INodeType { { name: 'AI Data Extraction', value: 'aiDataExtraction' }, { name: 'Brand Intelligence', value: 'brand' }, { name: 'Industry Classification', value: 'industry' }, + { name: 'Monitor', value: 'monitors' }, { name: 'Utility', value: 'utility' }, { name: 'Web Scraping', value: 'web' }, ], @@ -50,6 +52,7 @@ export class ContextDev implements INodeType { ...aiDataExtractionDescription, ...industryDescription, ...utilityDescription, + ...monitorsDescription, ], }; } diff --git a/nodes/ContextDev/resources/aiDataExtraction/index.ts b/nodes/ContextDev/resources/aiDataExtraction/index.ts index f49aa85..b692bf2 100644 --- a/nodes/ContextDev/resources/aiDataExtraction/index.ts +++ b/nodes/ContextDev/resources/aiDataExtraction/index.ts @@ -41,11 +41,32 @@ export const aiDataExtractionDescription: INodeProperties[] = [ name: 'domain', type: 'string', required: true, - displayOptions: { show: { ...show, operation: ['aiQuery', 'extractProducts'] } }, + displayOptions: { show: { ...show, operation: ['aiQuery'] } }, default: '', placeholder: 'stripe.com', routing: { request: { body: { domain: '={{ $value }}' } } }, }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { ...show, operation: ['extractProducts'] } }, + default: '', + placeholder: 'stripe.com', + description: 'The domain to crawl for products. Provide either this or Direct URL.', + routing: { request: { body: { domain: '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + displayOptions: { show: { ...show, operation: ['extractProducts'] } }, + default: '', + placeholder: 'https://example.com/shop', + description: + 'A specific URL to use directly as the starting point for extraction, without domain resolution. Provide either this or Domain.', + routing: { request: { body: { directUrl: '={{ $value || undefined }}' } } }, + }, { displayName: 'Product URL', name: 'url', @@ -93,9 +114,11 @@ export const aiDataExtractionDescription: INodeProperties[] = [ options: [ { name: 'Boolean', value: 'boolean' }, { name: 'Date', value: 'date' }, + { name: 'List', value: 'list' }, { name: 'Number', value: 'number' }, { name: 'Object', value: 'object' }, { name: 'String', value: 'string' }, + { name: 'Text', value: 'text' }, { name: 'URL', value: 'url' }, ] }, @@ -106,6 +129,15 @@ export const aiDataExtractionDescription: INodeProperties[] = [ default: '', placeholder: 'company_tagline', }, + { + displayName: 'Object Schema (JSON)', + name: 'datapoint_object_schema', + type: 'json', + default: '', + placeholder: '{ "price": "number", "currency": "string" }', + description: + "Used only when List Item Type is 'Object'. A JSON map of field name to scalar type ('string', 'number', 'date', or 'boolean') describing the shape of each object to extract.", + }, { displayName: 'Type', name: 'datapoint_type', @@ -123,7 +155,97 @@ export const aiDataExtractionDescription: INodeProperties[] = [ ], }, ], - routing: { request: { body: { data_to_extract: '={{ $value.datapoint }}' } } }, + routing: { + request: { + body: { + data_to_extract: + "={{ ($value.datapoint || []).map((dp) => ({ ...dp, datapoint_object_schema: dp.datapoint_object_schema ? JSON.parse(dp.datapoint_object_schema) : undefined })) }}", + }, + }, + }, + }, + { + displayName: 'Specific Pages', + name: 'specificPages', + type: 'collection', + placeholder: 'Add Page', + default: {}, + displayOptions: { show: { ...show, operation: ['aiQuery'] } }, + description: 'Restrict the analysis to specific page types', + options: [ + { + displayName: 'About Us', + name: 'about_us', + type: 'boolean', + default: false, + description: 'Whether to analyze the about us page', + routing: { send: { type: 'body', property: 'specific_pages.about_us' } }, + }, + { + displayName: 'Blog', + name: 'blog', + type: 'boolean', + default: false, + description: 'Whether to analyze the blog', + routing: { send: { type: 'body', property: 'specific_pages.blog' } }, + }, + { + displayName: 'Careers', + name: 'careers', + type: 'boolean', + default: false, + description: 'Whether to analyze the careers page', + routing: { send: { type: 'body', property: 'specific_pages.careers' } }, + }, + { + displayName: 'Contact Us', + name: 'contact_us', + type: 'boolean', + default: false, + description: 'Whether to analyze the contact us page', + routing: { send: { type: 'body', property: 'specific_pages.contact_us' } }, + }, + { + displayName: 'FAQ', + name: 'faq', + type: 'boolean', + default: false, + description: 'Whether to analyze the FAQ page', + routing: { send: { type: 'body', property: 'specific_pages.faq' } }, + }, + { + displayName: 'Home Page', + name: 'home_page', + type: 'boolean', + default: false, + description: 'Whether to analyze the home page', + routing: { send: { type: 'body', property: 'specific_pages.home_page' } }, + }, + { + displayName: 'Pricing', + name: 'pricing', + type: 'boolean', + default: false, + description: 'Whether to analyze the pricing page', + routing: { send: { type: 'body', property: 'specific_pages.pricing' } }, + }, + { + displayName: 'Privacy Policy', + name: 'privacy_policy', + type: 'boolean', + default: false, + description: 'Whether to analyze the privacy policy page', + routing: { send: { type: 'body', property: 'specific_pages.privacy_policy' } }, + }, + { + displayName: 'Terms and Conditions', + name: 'terms_and_conditions', + type: 'boolean', + default: false, + description: 'Whether to analyze the terms and conditions page', + routing: { send: { type: 'body', property: 'specific_pages.terms_and_conditions' } }, + }, + ], }, { displayName: 'Additional Fields', @@ -144,6 +266,17 @@ export const aiDataExtractionDescription: INodeProperties[] = [ typeOptions: { minValue: 1 }, routing: { request: { body: { maxProducts: '={{ $value }}' } } }, }, + { + displayName: 'Max Age (Ms)', + name: 'maxAgeMs', + type: 'number', + displayOptions: { show: { '/operation': ['extractProduct', 'extractProducts'] } }, + default: 604800000, + typeOptions: { minValue: 0, maxValue: 2592000000 }, + description: + 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 7 days. Max is 30 days. Set to 0 to always scrape fresh.', + routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, + }, { displayName: 'Pages to Analyze', name: 'specific_pages', @@ -176,7 +309,7 @@ export const aiDataExtractionDescription: INodeProperties[] = [ name: 'timeoutMS', type: 'number', default: 30000, - typeOptions: { minValue: 1, maxValue: 300000 }, + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, }, ], diff --git a/nodes/ContextDev/resources/brand/index.ts b/nodes/ContextDev/resources/brand/index.ts index 6454cf7..49859c0 100644 --- a/nodes/ContextDev/resources/brand/index.ts +++ b/nodes/ContextDev/resources/brand/index.ts @@ -165,7 +165,7 @@ export const brandDescription: INodeProperties[] = [ displayName: 'Country Code', name: 'country_gl', type: 'string', - displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, + displayOptions: { show: { '/operation': ['identifyFromTransaction', 'retrieveByName'] } }, default: '', placeholder: 'us', description: 'ISO 3166-1 alpha-2 country code', @@ -175,43 +175,168 @@ export const brandDescription: INodeProperties[] = [ displayName: 'Force Language', name: 'force_language', type: 'options', + displayOptions: { + show: { + '/operation': [ + 'identifyFromTransaction', + 'retrieve', + 'retrieveByEmail', + 'retrieveByName', + 'retrieveByTicker', + 'retrieveByIsin', + ], + }, + }, default: 'english', options: [ + { name: 'Afrikaans', value: 'afrikaans' }, + { name: 'Albanian', value: 'albanian' }, + { name: 'Amharic', value: 'amharic' }, { name: 'Arabic', value: 'arabic' }, + { name: 'Armenian', value: 'armenian' }, + { name: 'Assamese', value: 'assamese' }, + { name: 'Aymara', value: 'aymara' }, + { name: 'Azeri', value: 'azeri' }, + { name: 'Basque', value: 'basque' }, + { name: 'Belarusian', value: 'belarusian' }, + { name: 'Bengali', value: 'bengali' }, + { name: 'Bosnian', value: 'bosnian' }, + { name: 'Bulgarian', value: 'bulgarian' }, + { name: 'Burmese', value: 'burmese' }, + { name: 'Cantonese', value: 'cantonese' }, + { name: 'Catalan', value: 'catalan' }, + { name: 'Cebuano', value: 'cebuano' }, { name: 'Chinese', value: 'chinese' }, + { name: 'Corsican', value: 'corsican' }, + { name: 'Croatian', value: 'croatian' }, { name: 'Czech', value: 'czech' }, { name: 'Danish', value: 'danish' }, { name: 'Dutch', value: 'dutch' }, { name: 'English', value: 'english' }, + { name: 'Esperanto', value: 'esperanto' }, + { name: 'Estonian', value: 'estonian' }, + { name: 'Farsi', value: 'farsi' }, + { name: 'Fijian', value: 'fijian' }, { name: 'Finnish', value: 'finnish' }, { name: 'French', value: 'french' }, + { name: 'Galician', value: 'galician' }, + { name: 'Georgian', value: 'georgian' }, { name: 'German', value: 'german' }, { name: 'Greek', value: 'greek' }, + { name: 'Guarani', value: 'guarani' }, + { name: 'Gujarati', value: 'gujarati' }, + { name: 'Haitian Creole', value: 'haitian-creole' }, + { name: 'Hausa', value: 'hausa' }, + { name: 'Hawaiian', value: 'hawaiian' }, { name: 'Hebrew', value: 'hebrew' }, { name: 'Hindi', value: 'hindi' }, + { name: 'Hmong', value: 'hmong' }, { name: 'Hungarian', value: 'hungarian' }, + { name: 'Icelandic', value: 'icelandic' }, + { name: 'Igbo', value: 'igbo' }, { name: 'Indonesian', value: 'indonesian' }, + { name: 'Irish', value: 'irish' }, { name: 'Italian', value: 'italian' }, { name: 'Japanese', value: 'japanese' }, + { name: 'Javanese', value: 'javanese' }, + { name: 'Kannada', value: 'kannada' }, + { name: 'Kazakh', value: 'kazakh' }, + { name: 'Khmer', value: 'khmer' }, + { name: 'Kinyarwanda', value: 'kinyarwanda' }, { name: 'Korean', value: 'korean' }, + { name: 'Kurdish', value: 'kurdish' }, + { name: 'Kyrgyz', value: 'kyrgyz' }, + { name: 'Lao', value: 'lao' }, + { name: 'Latin', value: 'latin' }, + { name: 'Latvian', value: 'latvian' }, + { name: 'Lingala', value: 'lingala' }, + { name: 'Lithuanian', value: 'lithuanian' }, + { name: 'Luxembourgish', value: 'luxembourgish' }, + { name: 'Macedonian', value: 'macedonian' }, + { name: 'Malagasy', value: 'malagasy' }, + { name: 'Malay', value: 'malay' }, + { name: 'Malayalam', value: 'malayalam' }, + { name: 'Maltese', value: 'maltese' }, + { name: 'Maori', value: 'maori' }, + { name: 'Marathi', value: 'marathi' }, + { name: 'Mongolian', value: 'mongolian' }, + { name: 'Nepali', value: 'nepali' }, { name: 'Norwegian', value: 'norwegian' }, + { name: 'Odia', value: 'odia' }, + { name: 'Oromo', value: 'oromo' }, + { name: 'Pashto', value: 'pashto' }, + { name: 'Pidgin', value: 'pidgin' }, { name: 'Polish', value: 'polish' }, { name: 'Portuguese', value: 'portuguese' }, + { name: 'Punjabi', value: 'punjabi' }, + { name: 'Quechua', value: 'quechua' }, { name: 'Romanian', value: 'romanian' }, { name: 'Russian', value: 'russian' }, + { name: 'Samoan', value: 'samoan' }, + { name: 'Scottish Gaelic', value: 'scottish-gaelic' }, + { name: 'Serbian', value: 'serbian' }, + { name: 'Sesotho', value: 'sesotho' }, + { name: 'Shona', value: 'shona' }, + { name: 'Sindhi', value: 'sindhi' }, + { name: 'Sinhala', value: 'sinhala' }, + { name: 'Slovak', value: 'slovak' }, + { name: 'Slovene', value: 'slovene' }, + { name: 'Somali', value: 'somali' }, { name: 'Spanish', value: 'spanish' }, + { name: 'Sundanese', value: 'sundanese' }, + { name: 'Swahili', value: 'swahili' }, { name: 'Swedish', value: 'swedish' }, + { name: 'Tagalog', value: 'tagalog' }, + { name: 'Tajik', value: 'tajik' }, + { name: 'Tamil', value: 'tamil' }, + { name: 'Tatar', value: 'tatar' }, + { name: 'Telugu', value: 'telugu' }, { name: 'Thai', value: 'thai' }, + { name: 'Tibetan', value: 'tibetan' }, + { name: 'Tigrinya', value: 'tigrinya' }, + { name: 'Tongan', value: 'tongan' }, + { name: 'Tswana', value: 'tswana' }, { name: 'Turkish', value: 'turkish' }, + { name: 'Turkmen', value: 'turkmen' }, { name: 'Ukrainian', value: 'ukrainian' }, + { name: 'Urdu', value: 'urdu' }, + { name: 'Uyghur', value: 'uyghur' }, + { name: 'Uzbek', value: 'uzbek' }, { name: 'Vietnamese', value: 'vietnamese' }, + { name: 'Welsh', value: 'welsh' }, + { name: 'Wolof', value: 'wolof' }, + { name: 'Xhosa', value: 'xhosa' }, + { name: 'Yiddish', value: 'yiddish' }, + { name: 'Yoruba', value: 'yoruba' }, + { name: 'Zulu', value: 'zulu' }, ], routing: { request: { qs: { force_language: '={{ $value }}' } } }, }, + { + displayName: 'High Confidence Only', + name: 'high_confidence_only', + type: 'boolean', + displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, + default: false, + description: 'Whether to only return a brand match when confidence is high', + routing: { request: { qs: { high_confidence_only: '={{ $value }}' } } }, + }, { displayName: 'Max Age (Ms)', name: 'maxAgeMs', type: 'number', + displayOptions: { + show: { + '/operation': [ + 'retrieve', + 'retrieveByEmail', + 'retrieveByName', + 'retrieveByTicker', + 'retrieveByIsin', + 'retrieveSimplified', + ], + }, + }, default: 7776000000, description: 'Max age of cached data in ms before a hard refresh. Default 3 months.', typeOptions: { minValue: 86400000, maxValue: 31536000000 }, @@ -221,6 +346,18 @@ export const brandDescription: INodeProperties[] = [ displayName: 'Max Speed', name: 'maxSpeed', type: 'boolean', + displayOptions: { + show: { + '/operation': [ + 'identifyFromTransaction', + 'retrieve', + 'retrieveByEmail', + 'retrieveByName', + 'retrieveByTicker', + 'retrieveByIsin', + ], + }, + }, default: false, description: 'Whether to skip time-consuming operations for a faster response at the cost of less data', routing: { request: { qs: { maxSpeed: '={{ $value }}' } } }, @@ -235,6 +372,19 @@ export const brandDescription: INodeProperties[] = [ description: 'Merchant Category Code', routing: { request: { qs: { mcc: '={{ $value }}' } } }, }, + { + // SDK types `phone` as number; we deliberately expose it as a string so users can + // enter real phone formats (leading +, spaces) that aren't valid JS numbers. The + // value is passed through verbatim as the `phone` query param. + displayName: 'Phone', + name: 'phone', + type: 'string', + displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, + default: '', + placeholder: '+14155551234', + description: 'Company phone number to help identify the brand', + routing: { request: { qs: { phone: '={{ $value }}' } } }, + }, { displayName: 'Ticker Exchange', name: 'ticker_exchange', @@ -242,18 +392,78 @@ export const brandDescription: INodeProperties[] = [ displayOptions: { show: { '/operation': ['retrieveByTicker'] } }, default: 'NASDAQ', options: [ - { name: 'AMEX', value: 'AMEX' }, + { name: 'AMEX (NYSE American)', value: 'AMEX' }, + { name: 'AMS (Amsterdam)', value: 'AMS' }, + { name: 'AQS (AQSE, UK)', value: 'AQS' }, { name: 'ASX (Australia)', value: 'ASX' }, + { name: 'ATH (Athens)', value: 'ATH' }, + { name: 'BER (Berlin)', value: 'BER' }, + { name: 'BME (Madrid)', value: 'BME' }, + { name: 'BRU (Brussels)', value: 'BRU' }, + { name: 'BSE (Bombay)', value: 'BSE' }, + { name: 'BUD (Budapest)', value: 'BUD' }, + { name: 'BUE (Buenos Aires)', value: 'BUE' }, + { name: 'BVC (Colombia)', value: 'BVC' }, + { name: 'CBOE (Chicago)', value: 'CBOE' }, + { name: 'CNQ (Canadian Securities Exchange)', value: 'CNQ' }, + { name: 'CPH (Copenhagen)', value: 'CPH' }, + { name: 'DFM (Dubai)', value: 'DFM' }, + { name: 'DOH (Doha)', value: 'DOH' }, + { name: 'DUB (Dublin)', value: 'DUB' }, + { name: 'DUS (Dusseldorf)', value: 'DUS' }, + { name: 'DXE (Cboe Europe Equities)', value: 'DXE' }, + { name: 'EGX (Egypt)', value: 'EGX' }, { name: 'FSX (Frankfurt)', value: 'FSX' }, + { name: 'HAM (Hamburg)', value: 'HAM' }, + { name: 'HEL (Helsinki)', value: 'HEL' }, { name: 'HKSE (Hong Kong)', value: 'HKSE' }, + { name: 'HOSE (Ho Chi Minh)', value: 'HOSE' }, + { name: 'ICE (Iceland)', value: 'ICE' }, + { name: 'IOB (International Order Book, LSE)', value: 'IOB' }, + { name: 'IST (Istanbul)', value: 'IST' }, + { name: 'JKT (Jakarta)', value: 'JKT' }, + { name: 'JNB (Johannesburg)', value: 'JNB' }, { name: 'JPX (Tokyo)', value: 'JPX' }, + { name: 'KLS (Kuala Lumpur)', value: 'KLS' }, + { name: 'KOE (Korea OTC/KONEX)', value: 'KOE' }, + { name: 'KSC (Korea Stock Exchange - KOSPI)', value: 'KSC' }, + { name: 'KUW (Kuwait)', value: 'KUW' }, + { name: 'LIS (Lisbon)', value: 'LIS' }, { name: 'LSE (London)', value: 'LSE' }, + { name: 'MCX (Moscow)', value: 'MCX' }, + { name: 'MEX (Mexico)', value: 'MEX' }, + { name: 'MIL (Milan)', value: 'MIL' }, + { name: 'MUN (Munich)', value: 'MUN' }, { name: 'NASDAQ', value: 'NASDAQ' }, + { name: 'NEO (Canada)', value: 'NEO' }, { name: 'NSE (India)', value: 'NSE' }, { name: 'NYSE', value: 'NYSE' }, - { name: 'SGX (Singapore)', value: 'SGX' }, - { name: 'SSE (Shanghai)', value: 'SSE' }, + { name: 'NZE (New Zealand)', value: 'NZE' }, + { name: 'OSL (Oslo)', value: 'OSL' }, + { name: 'OTC (Over-the-Counter, US)', value: 'OTC' }, + { name: 'PAR (Paris)', value: 'PAR' }, + { name: 'PNK (Pink Sheets, US)', value: 'PNK' }, + { name: 'PRA (Prague)', value: 'PRA' }, + { name: 'RIS (Riga)', value: 'RIS' }, + { name: 'SAO (Sao Paulo)', value: 'SAO' }, + { name: 'SAU (Saudi Arabia)', value: 'SAU' }, + { name: 'SES (Singapore)', value: 'SES' }, + { name: 'SET (Thailand)', value: 'SET' }, + { name: 'SGO (Santiago)', value: 'SGO' }, + { name: 'SHH (Shanghai)', value: 'SHH' }, + { name: 'SHZ (Shenzhen)', value: 'SHZ' }, + { name: 'SIX (Switzerland)', value: 'SIX' }, + { name: 'STO (Stockholm)', value: 'STO' }, + { name: 'STU (Stuttgart)', value: 'STU' }, + { name: 'TAI (Taiwan)', value: 'TAI' }, + { name: 'TAL (Tallinn)', value: 'TAL' }, + { name: 'TLV (Tel Aviv)', value: 'TLV' }, { name: 'TSX (Toronto)', value: 'TSX' }, + { name: 'TSXV (TSX Venture)', value: 'TSXV' }, + { name: 'TWO (Taipei OTC)', value: 'TWO' }, + { name: 'VIE (Vienna)', value: 'VIE' }, + { name: 'WSE (Warsaw)', value: 'WSE' }, + { name: 'XETRA (Deutsche Börse)', value: 'XETRA' }, ], routing: { request: { qs: { ticker_exchange: '={{ $value }}' } } }, }, @@ -262,7 +472,7 @@ export const brandDescription: INodeProperties[] = [ name: 'timeoutMS', type: 'number', default: 30000, - typeOptions: { minValue: 1, maxValue: 300000 }, + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, }, ], diff --git a/nodes/ContextDev/resources/industry/index.ts b/nodes/ContextDev/resources/industry/index.ts index e10d413..c1e3dfc 100644 --- a/nodes/ContextDev/resources/industry/index.ts +++ b/nodes/ContextDev/resources/industry/index.ts @@ -81,7 +81,7 @@ export const industryDescription: INodeProperties[] = [ name: 'timeoutMS', type: 'number', default: 30000, - typeOptions: { minValue: 1, maxValue: 300000 }, + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, }, ], diff --git a/nodes/ContextDev/resources/monitors/index.ts b/nodes/ContextDev/resources/monitors/index.ts new file mode 100644 index 0000000..c81a4c4 --- /dev/null +++ b/nodes/ContextDev/resources/monitors/index.ts @@ -0,0 +1,737 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const show = { resource: ['monitors'] }; + +// ops that carry a monitor_id path param +const idOps = ['get', 'update', 'delete', 'run', 'listChanges', 'listRuns']; +// list-family ops (pagination + filters) +const listOps = ['list', 'listAccountChanges', 'listAccountRuns', 'listChanges', 'listRuns']; + +// shared literals (create + update reuse these) +const UNIT_OPTIONS = [ + { name: 'Minutes', value: 'minutes' }, + { name: 'Hours', value: 'hours' }, + { name: 'Days', value: 'days' }, +]; +const CONFIDENCE_OPTS = { minValue: 0, maxValue: 1, numberPrecision: 2 }; +// comma-separated string -> trimmed non-empty array, sent on body key "tags" +const tagsRouting = { + request: { body: { tags: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' } }, +}; + +const operationField: INodeProperties = { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show }, + options: [ + { + name: 'Create Monitor', + value: 'create', + action: 'Create a monitor', + description: 'Create a website monitor that watches a page, sitemap, or extraction target for changes', + routing: { request: { method: 'POST', url: '/monitors' } }, + }, + { + name: 'Delete Monitor', + value: 'delete', + action: 'Delete a monitor', + description: 'Delete a monitor by ID', + routing: { request: { method: 'DELETE', url: '=/monitors/{{ $parameter["monitorId"] }}' } }, + }, + { + name: 'Get Change', + value: 'getChange', + action: 'Get a single change', + description: 'Retrieve a single detected change by its ID', + routing: { request: { method: 'GET', url: '=/monitors/changes/{{ $parameter["changeId"] }}' } }, + }, + { + name: 'Get Monitor', + value: 'get', + action: 'Get a monitor', + description: 'Retrieve a monitor by ID', + routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}' } }, + }, + { + name: 'List Account Changes', + value: 'listAccountChanges', + action: 'List changes across all monitors', + description: 'List detected changes across every monitor in the account', + routing: { request: { method: 'GET', url: '/monitors/changes' } }, + }, + { + name: 'List Account Runs', + value: 'listAccountRuns', + action: 'List runs across all monitors', + description: 'List monitor runs across every monitor in the account', + routing: { request: { method: 'GET', url: '/monitors/runs' } }, + }, + { + name: 'List Monitor Changes', + value: 'listChanges', + action: 'List changes for a monitor', + description: 'List detected changes for a single monitor', + routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/changes' } }, + }, + { + name: 'List Monitor Runs', + value: 'listRuns', + action: 'List runs for a monitor', + description: 'List runs for a single monitor', + routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/runs' } }, + }, + { + name: 'List Monitors', + value: 'list', + action: 'List monitors', + description: 'List monitors in the account', + routing: { request: { method: 'GET', url: '/monitors' } }, + }, + { + name: 'Run Monitor', + value: 'run', + action: 'Run a monitor now', + description: 'Trigger an immediate run of a monitor', + routing: { request: { method: 'POST', url: '=/monitors/{{ $parameter["monitorId"] }}/run' } }, + }, + { + name: 'Update Monitor', + value: 'update', + action: 'Update a monitor', + description: 'Update a monitor by ID', + routing: { request: { method: 'PATCH', url: '=/monitors/{{ $parameter["monitorId"] }}' } }, + }, + ], + default: 'list', +}; + +// ─── path params ──────────────────────────────────────────────────────────────── +const pathParams: INodeProperties[] = [ + { + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + placeholder: 'mon_123', + displayOptions: { show: { ...show, operation: idOps } }, + }, + { + displayName: 'Change ID', + name: 'changeId', + type: 'string', + required: true, + default: '', + placeholder: 'chg_123', + displayOptions: { show: { ...show, operation: ['getChange'] } }, + }, +]; + +// ─── create: core + discriminators ──────────────────────────────────────────────── +const createCore: INodeProperties[] = [ + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + placeholder: 'Pricing page watcher', + displayOptions: { show: { ...show, operation: ['create'] } }, + routing: { send: { type: 'body', property: 'name' } }, + }, + { + displayName: 'Target Type', + name: 'targetType', + type: 'options', + default: 'page', + description: 'What to monitor. Sitemap targets pair with exact detection; extract targets pair with semantic detection.', + displayOptions: { show: { ...show, operation: ['create'] } }, + options: [ + { name: 'Page', value: 'page' }, + { name: 'Sitemap', value: 'sitemap' }, + { name: 'Extract', value: 'extract' }, + ], + routing: { send: { type: 'body', property: 'target.type' } }, + }, + { + displayName: 'Target URL', + name: 'targetUrl', + type: 'string', + required: true, + default: '', + placeholder: 'https://stripe.com/pricing', + displayOptions: { show: { ...show, operation: ['create'] } }, + routing: { send: { type: 'body', property: 'target.url' } }, + }, + { + displayName: 'Normalize Whitespace', + name: 'normalizeWhitespace', + type: 'boolean', + default: false, + description: 'Whether to normalize whitespace before comparing or analyzing text', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['page'] } }, + routing: { send: { type: 'body', property: 'target.normalize_whitespace' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'followSubdomains', + type: 'boolean', + default: false, + description: 'Whether to follow links into subdomains while extracting', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, + routing: { send: { type: 'body', property: 'target.follow_subdomains' } }, + }, + { + displayName: 'Extract Instructions', + name: 'extractPrompt', + type: 'string', + default: '', + description: 'Optional natural-language instructions guiding what to extract', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, + routing: { send: { type: 'body', property: 'target.instructions' } }, + }, + { + displayName: 'Extract Schema (JSON)', + name: 'extractSchema', + type: 'json', + default: '', + description: 'Optional JSON Schema for the structured data to extract. Defaults to a summary + key-points schema.', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, + routing: { send: { type: 'body', property: 'target.schema', value: '={{ JSON.parse($value) }}' } }, + }, + { + displayName: 'Max Depth', + name: 'extractMaxDepth', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Optional maximum link depth from the starting URL (0 = only the starting page)', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, + routing: { send: { type: 'body', property: 'target.max_depth' } }, + }, + { + displayName: 'Max Pages', + name: 'extractMaxPages', + type: 'number', + default: 10, + typeOptions: { minValue: 1 }, + description: 'Maximum number of pages to analyze during extraction', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, + routing: { send: { type: 'body', property: 'target.max_pages' } }, + }, + { + displayName: 'Exclude Patterns', + name: 'sitemapExclude', + type: 'string', + default: '', + placeholder: '/blog/*,/legal/*', + description: 'Comma-separated URL path patterns to exclude', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['sitemap'] } }, + routing: { + send: { + type: 'body', + property: 'target.exclude', + value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + { + displayName: 'Include Patterns', + name: 'sitemapInclude', + type: 'string', + default: '', + placeholder: '/pricing/*', + description: 'Comma-separated URL path patterns to include', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['sitemap'] } }, + routing: { + send: { + type: 'body', + property: 'target.include', + value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + { + displayName: 'Max URLs', + name: 'sitemapMaxUrls', + type: 'number', + default: 1000, + typeOptions: { minValue: 1 }, + description: 'Maximum number of URLs to monitor from the sitemap', + displayOptions: { show: { ...show, operation: ['create'], targetType: ['sitemap'] } }, + routing: { send: { type: 'body', property: 'target.max_urls' } }, + }, + { + displayName: 'Change Detection', + name: 'changeDetectionType', + type: 'options', + default: 'exact', + displayOptions: { show: { ...show, operation: ['create'] } }, + options: [ + { name: 'Exact', value: 'exact' }, + { name: 'Semantic', value: 'semantic' }, + ], + routing: { send: { type: 'body', property: 'change_detection.type' } }, + }, + { + displayName: 'Semantic Query', + name: 'semanticQuery', + type: 'string', + required: true, + default: '', + placeholder: 'the monthly price of the Pro plan', + description: 'Natural-language query describing the meaning-level change to watch for', + displayOptions: { show: { ...show, operation: ['create'], changeDetectionType: ['semantic'] } }, + routing: { send: { type: 'body', property: 'change_detection.query' } }, + }, + { + displayName: 'Confidence Threshold', + name: 'confidenceThreshold', + type: 'number', + default: 0.5, + typeOptions: CONFIDENCE_OPTS, + description: 'Minimum confidence (0–1) required to report a semantic change', + displayOptions: { show: { ...show, operation: ['create'], changeDetectionType: ['semantic'] } }, + routing: { send: { type: 'body', property: 'change_detection.confidence_threshold' } }, + }, + { + displayName: 'Schedule Frequency', + name: 'scheduleFrequency', + type: 'number', + required: true, + default: 6, + typeOptions: { minValue: 1 }, + description: 'Number of units between runs. The total interval (frequency × unit) must be between 10 minutes and 1 year.', + displayOptions: { show: { ...show, operation: ['create'] } }, + routing: { send: { type: 'body', property: 'schedule.frequency' } }, + }, + { + displayName: 'Schedule Unit', + name: 'scheduleUnit', + type: 'options', + default: 'hours', + displayOptions: { show: { ...show, operation: ['create'] } }, + options: UNIT_OPTIONS, + routing: { send: { type: 'body', property: 'schedule.unit' } }, + }, + { + displayName: 'Schedule Type', + name: 'scheduleType', + type: 'hidden', + default: 'interval', + displayOptions: { show: { ...show, operation: ['create'] } }, + routing: { send: { type: 'body', property: 'schedule.type' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: ['create'] } }, + options: [ + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + placeholder: 'pricing,marketing', + description: 'Comma-separated tags for grouping and filtering monitors and their changes', + routing: tagsRouting, + }, + { + displayName: 'Webhook Secret', + name: 'webhookSecret', + type: 'string', + typeOptions: { password: true }, + default: '', + description: + 'Signing secret used to verify webhook authenticity (each delivery is HMAC-signed with it)', + routing: { send: { type: 'body', property: 'webhook.secret' } }, + }, + { + displayName: 'Webhook URL', + name: 'webhookUrl', + type: 'string', + default: '', + placeholder: 'https://example.com/hooks/context', + description: 'URL called when a change is detected', + routing: { send: { type: 'body', property: 'webhook.url' } }, + }, + ], + }, +]; + +// ─── update: all-optional mutable fields ────────────────────────────────────────── +const updateFields: INodeProperties[] = [ + { + displayName: 'Update Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { ...show, operation: ['update'] } }, + options: [ + { + displayName: 'Change Detection Query', + name: 'updQuery', + type: 'string', + default: '', + description: 'Natural-language query for semantic detection', + routing: { send: { type: 'body', property: 'change_detection.query' } }, + }, + { + displayName: 'Change Detection Type', + name: 'updChangeDetectionType', + type: 'options', + default: 'exact', + description: 'Switch how changes are detected. Changing this creates a new baseline.', + options: [ + { name: 'Exact', value: 'exact' }, + { name: 'Semantic', value: 'semantic' }, + ], + routing: { send: { type: 'body', property: 'change_detection.type' } }, + }, + { + displayName: 'Confidence Threshold', + name: 'updConfidence', + type: 'number', + default: 0.5, + typeOptions: CONFIDENCE_OPTS, + routing: { send: { type: 'body', property: 'change_detection.confidence_threshold' } }, + }, + { + displayName: 'Extract Instructions', + name: 'updExtractInstructions', + type: 'string', + default: '', + description: 'Optional natural-language instructions guiding what to extract', + routing: { send: { type: 'body', property: 'target.instructions' } }, + }, + { + displayName: 'Extract Max Depth', + name: 'updExtractMaxDepth', + type: 'number', + default: 0, + typeOptions: { minValue: 0 }, + description: 'Optional maximum link depth from the starting URL (0 = only the starting page)', + routing: { send: { type: 'body', property: 'target.max_depth' } }, + }, + { + displayName: 'Extract Max Pages', + name: 'updExtractMaxPages', + type: 'number', + default: 10, + typeOptions: { minValue: 1 }, + description: 'Maximum number of pages to analyze during extraction', + routing: { send: { type: 'body', property: 'target.max_pages' } }, + }, + { + displayName: 'Extract Schema (JSON)', + name: 'updExtractSchema', + type: 'json', + default: '', + description: 'JSON Schema describing the structured data to extract and watch for changes', + routing: { send: { type: 'body', property: 'target.schema', value: '={{ JSON.parse($value) }}' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'updFollowSubdomains', + type: 'boolean', + default: false, + description: 'Whether to follow links into subdomains while extracting', + routing: { send: { type: 'body', property: 'target.follow_subdomains' } }, + }, + { + displayName: 'Name', + name: 'updName', + type: 'string', + default: '', + routing: { send: { type: 'body', property: 'name' } }, + }, + { + displayName: 'Normalize Whitespace', + name: 'updNormalizeWhitespace', + type: 'boolean', + default: false, + description: 'Whether to normalize whitespace before comparing or analyzing text', + routing: { send: { type: 'body', property: 'target.normalize_whitespace' } }, + }, + { + displayName: 'Remove Webhook', + name: 'updWebhookRemove', + type: 'boolean', + default: false, + description: 'Whether to remove the monitor\'s existing webhook (sends webhook: null)', + routing: { request: { body: { webhook: '={{ $value ? null : undefined }}' } } }, + }, + { + // The SDK requires schedule.type whenever schedule is sent, so frequency + unit + // are grouped and the constant type is injected — sent only when the group is set. + displayName: 'Schedule', + name: 'updSchedule', + type: 'fixedCollection', + default: {}, + description: 'Change the run interval (the required schedule type is sent automatically)', + options: [ + { + name: 'schedule', + displayName: 'Schedule', + values: [ + { + displayName: 'Frequency', + name: 'frequency', + type: 'number', + default: 6, + typeOptions: { minValue: 1 }, + }, + { + displayName: 'Unit', + name: 'unit', + type: 'options', + default: 'hours', + options: UNIT_OPTIONS, + }, + ], + }, + ], + routing: { + request: { + body: { + schedule: + '={{ { type: "interval", frequency: $value.schedule.frequency, unit: $value.schedule.unit } }}', + }, + }, + }, + }, + { + displayName: 'Sitemap Exclude Patterns', + name: 'updSitemapExclude', + type: 'string', + default: '', + placeholder: '/blog/*,/legal/*', + description: 'Comma-separated URL path patterns to exclude', + routing: { + send: { + type: 'body', + property: 'target.exclude', + value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + { + displayName: 'Sitemap Include Patterns', + name: 'updSitemapInclude', + type: 'string', + default: '', + placeholder: '/pricing/*', + description: 'Comma-separated URL path patterns to include', + routing: { + send: { + type: 'body', + property: 'target.include', + value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + { + displayName: 'Sitemap Max URLs', + name: 'updSitemapMaxUrls', + type: 'number', + default: 1000, + typeOptions: { minValue: 1 }, + description: 'Maximum number of URLs to monitor from the sitemap', + routing: { send: { type: 'body', property: 'target.max_urls' } }, + }, + { + displayName: 'Status', + name: 'updStatus', + type: 'options', + default: 'active', + options: [ + { name: 'Active', value: 'active' }, + { name: 'Paused', value: 'paused' }, + ], + routing: { send: { type: 'body', property: 'status' } }, + }, + { + displayName: 'Tags', + name: 'updTags', + type: 'string', + default: '', + placeholder: 'pricing,marketing', + description: 'Comma-separated tags', + routing: tagsRouting, + }, + { + displayName: 'Target Type', + name: 'updTargetType', + type: 'options', + default: 'page', + description: 'Switch what the monitor watches. Changing this creates a new baseline.', + options: [ + { name: 'Page', value: 'page' }, + { name: 'Sitemap', value: 'sitemap' }, + { name: 'Extract', value: 'extract' }, + ], + routing: { send: { type: 'body', property: 'target.type' } }, + }, + { + displayName: 'Target URL', + name: 'updTargetUrl', + type: 'string', + default: '', + routing: { send: { type: 'body', property: 'target.url' } }, + }, + { + displayName: 'Webhook Secret', + name: 'updWebhookSecret', + type: 'string', + typeOptions: { password: true }, + default: '', + description: 'Signing secret used to verify webhook authenticity', + routing: { send: { type: 'body', property: 'webhook.secret' } }, + }, + { + displayName: 'Webhook URL', + name: 'updWebhookUrl', + type: 'string', + default: '', + routing: { send: { type: 'body', property: 'webhook.url' } }, + }, + ], + }, +]; + +// ─── list / changes / runs: pagination + filters ───────────────────────────────── +const runStatusOptions = [ + { name: 'Queued', value: 'queued' }, + { name: 'Running', value: 'running' }, + { name: 'Completed', value: 'completed' }, + { name: 'Failed', value: 'failed' }, +]; + +const listFilters: INodeProperties[] = [ + { + displayName: 'Filters', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { show: { ...show, operation: listOps } }, + options: [ + { + displayName: 'Change Detection Type', + name: 'change_detection_type', + type: 'options', + default: 'exact', + displayOptions: { show: { '/operation': ['list', 'listAccountChanges'] } }, + options: [ + { name: 'Exact', value: 'exact' }, + { name: 'Semantic', value: 'semantic' }, + ], + routing: { request: { qs: { change_detection_type: '={{ $value }}' } } }, + }, + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: '', + description: 'Pagination cursor from a previous response', + routing: { request: { qs: { cursor: '={{ $value }}' } } }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + typeOptions: { minValue: 1 }, + description: 'Max number of results to return', + routing: { request: { qs: { limit: '={{ $value }}' } } }, + }, + { + displayName: 'Monitor ID', + name: 'monitor_id', + type: 'string', + default: '', + description: 'Filter account-wide changes to a single monitor', + displayOptions: { show: { '/operation': ['listAccountChanges'] } }, + routing: { request: { qs: { monitor_id: '={{ $value }}' } } }, + }, + { + displayName: 'Run Status', + name: 'runStatus', + type: 'options', + default: 'completed', + displayOptions: { show: { '/operation': ['listAccountRuns', 'listRuns'] } }, + options: runStatusOptions, + routing: { request: { qs: { status: '={{ $value }}' } } }, + }, + { + displayName: 'Since', + name: 'since', + type: 'string', + default: '', + placeholder: '2026-01-01T00:00:00Z', + description: 'Only return changes at or after this ISO 8601 timestamp', + displayOptions: { show: { '/operation': ['listAccountChanges', 'listChanges'] } }, + routing: { request: { qs: { since: '={{ $value }}' } } }, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + default: 'active', + displayOptions: { show: { '/operation': ['list'] } }, + options: [ + { name: 'Active', value: 'active' }, + { name: 'Paused', value: 'paused' }, + { name: 'Failed', value: 'failed' }, + ], + routing: { request: { qs: { status: '={{ $value }}' } } }, + }, + { + displayName: 'Tag', + name: 'tag', + type: 'string', + default: '', + description: 'Filter by a single tag', + displayOptions: { show: { '/operation': ['list', 'listAccountChanges', 'listChanges'] } }, + routing: { request: { qs: { tag: '={{ $value }}' } } }, + }, + { + displayName: 'Target Type', + name: 'target_type', + type: 'options', + default: 'page', + displayOptions: { show: { '/operation': ['list', 'listAccountChanges'] } }, + options: [ + { name: 'Page', value: 'page' }, + { name: 'Sitemap', value: 'sitemap' }, + { name: 'Extract', value: 'extract' }, + ], + routing: { request: { qs: { target_type: '={{ $value }}' } } }, + }, + { + displayName: 'Until', + name: 'until', + type: 'string', + default: '', + placeholder: '2026-02-01T00:00:00Z', + description: 'Only return changes at or before this ISO 8601 timestamp', + displayOptions: { show: { '/operation': ['listAccountChanges', 'listChanges'] } }, + routing: { request: { qs: { until: '={{ $value }}' } } }, + }, + ], + }, +]; + +export const monitorsDescription: INodeProperties[] = [ + operationField, + ...pathParams, + ...createCore, + ...updateFields, + ...listFilters, +]; diff --git a/nodes/ContextDev/resources/utility/index.ts b/nodes/ContextDev/resources/utility/index.ts index 07a9939..994f2d1 100644 --- a/nodes/ContextDev/resources/utility/index.ts +++ b/nodes/ContextDev/resources/utility/index.ts @@ -63,7 +63,7 @@ export const utilityDescription: INodeProperties[] = [ name: 'timeoutMS', type: 'number', default: 30000, - typeOptions: { minValue: 1, maxValue: 300000 }, + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, }, ], diff --git a/nodes/ContextDev/resources/web/brand.ts b/nodes/ContextDev/resources/web/brand.ts index 891e9fe..1077287 100644 --- a/nodes/ContextDev/resources/web/brand.ts +++ b/nodes/ContextDev/resources/web/brand.ts @@ -5,14 +5,16 @@ const visualOps = ['screenshot', 'extractStyleguide', 'extractFonts', 'extractCo export const brandFields: INodeProperties[] = [ { + // The SDK accepts EITHER domain OR directUrl (not both), so domain is not required — + // an empty value is omitted so a directUrl-only request is possible. displayName: 'Domain', name: 'domain', type: 'string', - required: true, displayOptions: { show: { ...show, operation: visualOps } }, default: '', placeholder: 'stripe.com', - routing: { request: { qs: { domain: '={{ $value }}' } } }, + description: 'Company domain. Provide either Domain or Direct URL (in Additional Fields), not both.', + routing: { request: { qs: { domain: '={{ $value || undefined }}' } } }, }, { @@ -23,15 +25,44 @@ export const brandFields: INodeProperties[] = [ default: {}, displayOptions: { show: { ...show, operation: visualOps } }, options: [ + { + displayName: 'Color Scheme', + name: 'colorScheme', + type: 'options', + displayOptions: { show: { '/operation': ['screenshot', 'extractStyleguide'] } }, + default: '', + options: [ + { name: 'Auto (Default)', value: '' }, + { name: 'Light', value: 'light' }, + { name: 'Dark', value: 'dark' }, + ], + description: 'Preferred color scheme to request from the target page', + routing: { request: { qs: { colorScheme: '={{ $value }}' } } }, + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + displayOptions: { show: { '/operation': ['screenshot'] } }, + default: '', + placeholder: 'us', + description: + 'Two-letter ISO 3166-1 alpha-2 country code to fetch the page from that location', + routing: { request: { qs: { country: '={{ $value }}' } } }, + }, { displayName: 'Direct URL', name: 'directUrl', type: 'string', - displayOptions: { show: { '/operation': ['screenshot', 'extractStyleguide', 'extractFonts'] } }, + displayOptions: { + show: { + '/operation': ['screenshot', 'extractStyleguide', 'extractFonts'], + }, + }, default: '', placeholder: 'https://example.com/design-system', - description: 'Fetch from this exact URL instead of resolving from the domain', - routing: { request: { qs: { directUrl: '={{ $value }}' } } }, + description: 'Fetch from this exact URL instead of resolving from the domain. Alternative to Domain (not both).', + routing: { request: { qs: { directUrl: '={{ $value || undefined }}' } } }, }, { displayName: 'Full Page Screenshot', @@ -39,7 +70,7 @@ export const brandFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['screenshot'] } }, default: false, - description: 'Capture the full scrollable page height instead of just the visible viewport', + description: 'Whether to capture the full scrollable page height instead of just the visible viewport', routing: { request: { qs: { fullScreenshot: '={{ $value ? "true" : "false" }}' } } }, }, { @@ -48,13 +79,14 @@ export const brandFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['screenshot'] } }, default: false, - description: 'Attempt to dismiss cookie consent banners before taking the screenshot', + description: 'Whether to attempt to dismiss cookie consent banners before taking the screenshot', routing: { request: { qs: { handleCookiePopup: '={{ $value ? "true" : "false" }}' } } }, }, { displayName: 'Max Age (Ms)', name: 'maxAgeMs', type: 'number', + displayOptions: { show: { '/operation': ['screenshot', 'extractStyleguide', 'extractFonts'] } }, default: 86400000, description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, @@ -87,15 +119,54 @@ export const brandFields: INodeProperties[] = [ ], routing: { request: { qs: { page: '={{ $value }}' } } }, }, + { + displayName: 'Scroll Offset', + name: 'scrollOffset', + type: 'number', + displayOptions: { show: { '/operation': ['screenshot'] } }, + default: 0, + description: + 'Vertical scroll offset in pixels to capture a long page in viewport-sized slices. Takes precedence over Full Page Screenshot. Max 100,000.', + typeOptions: { minValue: 0, maxValue: 100000 }, + routing: { request: { qs: { scrollOffset: '={{ $value }}' } } }, + }, { displayName: 'Timeout (Ms)', name: 'timeoutMS', type: 'number', default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails.', - typeOptions: { minValue: 1, maxValue: 300000 }, + description: 'Maximum time in milliseconds to wait for a response before the request fails', + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, }, + { + displayName: 'Viewport', + name: 'viewport', + type: 'collection', + placeholder: 'Add Dimension', + default: {}, + displayOptions: { show: { '/operation': ['screenshot'] } }, + description: 'Browser viewport dimensions for the screenshot. Defaults to 1920x1080.', + options: [ + { + displayName: 'Width', + name: 'width', + type: 'number', + default: 1920, + description: 'Viewport width in pixels', + typeOptions: { minValue: 240, maxValue: 7680 }, + }, + { + displayName: 'Height', + name: 'height', + type: 'number', + default: 1080, + description: 'Viewport height in pixels', + typeOptions: { minValue: 240, maxValue: 4320 }, + }, + ], + routing: { request: { qs: { viewport: '={{ $value }}' } } }, + }, { displayName: 'Wait For (Ms)', name: 'waitForMs', diff --git a/nodes/ContextDev/resources/web/index.ts b/nodes/ContextDev/resources/web/index.ts index 3c64832..0aa3d3f 100644 --- a/nodes/ContextDev/resources/web/index.ts +++ b/nodes/ContextDev/resources/web/index.ts @@ -32,7 +32,7 @@ const operationField: INodeProperties = { name: 'Extract Competitors', value: 'extractCompetitors', action: 'Extract competitors for a domain', - description: 'Identify direct competitors for a given brand domain.', + description: 'Identify direct competitors for a given brand domain', routing: { request: { method: 'GET', url: '/web/competitors' } }, }, { diff --git a/nodes/ContextDev/resources/web/scraping.ts b/nodes/ContextDev/resources/web/scraping.ts index caeb9dd..eedf2c1 100644 --- a/nodes/ContextDev/resources/web/scraping.ts +++ b/nodes/ContextDev/resources/web/scraping.ts @@ -43,13 +43,137 @@ export const scrapingFields: INodeProperties[] = [ default: {}, displayOptions: { show: { ...show, operation: scrapingOps } }, options: [ + { + displayName: 'Country', + name: 'country', + type: 'string', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, + default: '', + placeholder: 'us', + description: + 'Two-letter ISO 3166-1 alpha-2 country code to fetch the page from that location', + routing: { request: { qs: { country: '={{ $value }}' } } }, + }, + { + displayName: 'Country', + name: 'countryPost', + type: 'string', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: '', + placeholder: 'us', + description: + 'Two-letter ISO 3166-1 alpha-2 country code to fetch pages from that location', + routing: { request: { body: { country: '={{ $value }}' } } }, + }, + { + displayName: 'Custom Headers', + name: 'headers', + type: 'fixedCollection', + typeOptions: { multipleValues: true }, + default: {}, + displayOptions: { + show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, + }, + description: + 'Custom HTTP headers forwarded to the target URL. Sending headers bypasses the cache.', + options: [ + { + name: 'header', + displayName: 'Header', + values: [ + { displayName: 'Name', name: 'name', type: 'string', default: '' }, + { displayName: 'Value', name: 'value', type: 'string', default: '' }, + ], + }, + ], + routing: { + request: { + qs: { + headers: + '={{ Object.fromEntries(($value.header || []).map(h => [h.name, h.value])) }}', + }, + }, + }, + }, + { + displayName: 'Enrichment', + name: 'enrichment', + type: 'collection', + placeholder: 'Add Enrichment Option', + default: {}, + displayOptions: { show: { '/operation': ['scrapeImages'] } }, + description: + 'Enrich each returned image. Enabling enrichment raises the call cost to 5 credits.', + options: [ + { + displayName: 'Classification', + name: 'classification', + type: 'boolean', + default: false, + description: 'Whether to classify each image by visual asset type', + }, + { + displayName: 'Hosted URL', + name: 'hostedUrl', + type: 'boolean', + default: false, + description: + 'Whether to host materializable images on the CDN and return their URL and MIME type', + }, + { + displayName: 'Max Time per Image (Ms)', + name: 'maxTimePerMs', + type: 'number', + default: 30000, + description: 'Per-image enrichment timeout in milliseconds. Default 30,000. Max 60,000.', + typeOptions: { minValue: 1, maxValue: 60000 }, + }, + { + displayName: 'Resolution', + name: 'resolution', + type: 'boolean', + default: false, + description: 'Whether to measure image width and height when possible', + }, + ], + routing: { request: { qs: { enrichment: '={{ $value }}' } } }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectors', + type: 'string', + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, + default: '', + description: 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner")', + routing: { + request: { + qs: { excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, + arrayFormat: 'comma', + }, + }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectorsPost', + type: 'string', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: '', + description: 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner")', + routing: { + request: { + body: { + excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + }, + }, + }, + }, { displayName: 'Follow Subdomains', name: 'followSubdomains', type: 'boolean', displayOptions: { show: { '/operation': ['crawl'] } }, default: false, - description: 'Follow links to subdomains (e.g. blog.example.com) during a crawl', + description: 'Whether to follow links to subdomains (e.g. blog.example.com) during a crawl', routing: { request: { body: { followSubdomains: '={{ $value }}' } } }, }, { @@ -58,7 +182,7 @@ export const scrapingFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, default: false, - description: 'Include content from iframes embedded in the page', + description: 'Whether to include content from iframes embedded in the page', routing: { request: { qs: { includeFrames: '={{ $value }}' } } }, }, { @@ -67,7 +191,7 @@ export const scrapingFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['crawl'] } }, default: false, - description: 'Include content from iframes embedded in the page', + description: 'Whether to include content from iframes embedded in the page', routing: { request: { body: { includeFrames: '={{ $value }}' } } }, }, { @@ -76,7 +200,7 @@ export const scrapingFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['scrapeMd'] } }, default: false, - description: 'Include image references in the returned Markdown output', + description: 'Whether to include image references in the returned Markdown output', routing: { request: { qs: { includeImages: '={{ $value }}' } } }, }, { @@ -85,7 +209,7 @@ export const scrapingFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['crawl'] } }, default: false, - description: 'Include image references in the returned Markdown output', + description: 'Whether to include image references in the returned Markdown output', routing: { request: { body: { includeImages: '={{ $value }}' } } }, }, { @@ -94,7 +218,7 @@ export const scrapingFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['scrapeMd'] } }, default: true, - description: 'Preserve hyperlinks in the returned Markdown output', + description: 'Whether to preserve hyperlinks in the returned Markdown output', routing: { request: { qs: { includeLinks: '={{ $value }}' } } }, }, { @@ -103,27 +227,9 @@ export const scrapingFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['crawl'] } }, default: true, - description: 'Preserve hyperlinks in the returned Markdown output', + description: 'Whether to preserve hyperlinks in the returned Markdown output', routing: { request: { body: { includeLinks: '={{ $value }}' } } }, }, - { - displayName: 'Main Content Only', - name: 'useMainContentOnly', - type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: false, - description: 'Strip navigation, headers, and footers — return only the main body content', - routing: { request: { qs: { useMainContentOnly: '={{ $value }}' } } }, - }, - { - displayName: 'Main Content Only', - name: 'useMainContentOnlyPost', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: false, - description: 'Strip navigation, headers, and footers — return only the main body content', - routing: { request: { body: { useMainContentOnly: '={{ $value }}' } } }, - }, { displayName: 'Include Selectors', name: 'includeSelectors', @@ -156,72 +262,29 @@ export const scrapingFields: INodeProperties[] = [ }, }, { - displayName: 'Exclude Selectors', - name: 'excludeSelectors', - type: 'string', + displayName: 'Main Content Only', + name: 'useMainContentOnly', + type: 'boolean', displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: '', - description: - 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner").', - routing: { - request: { - qs: { excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, - arrayFormat: 'comma', - }, - }, + default: false, + description: 'Whether to strip navigation, headers, and footers — return only the main body content', + routing: { request: { qs: { useMainContentOnly: '={{ $value }}' } } }, }, { - displayName: 'Exclude Selectors', - name: 'excludeSelectorsPost', - type: 'string', + displayName: 'Main Content Only', + name: 'useMainContentOnlyPost', + type: 'boolean', displayOptions: { show: { '/operation': ['crawl'] } }, - default: '', - description: - 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner").', - routing: { - request: { - body: { - excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', - }, - }, - }, - }, - { - displayName: 'Custom Headers', - name: 'headers', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - default: {}, - displayOptions: { - show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, - }, - description: - 'Custom HTTP headers forwarded to the target URL. Sending headers bypasses the cache.', - options: [ - { - name: 'header', - displayName: 'Header', - values: [ - { displayName: 'Name', name: 'name', type: 'string', default: '' }, - { displayName: 'Value', name: 'value', type: 'string', default: '' }, - ], - }, - ], - routing: { - request: { - qs: { - headers: - '={{ Object.fromEntries(($value.header || []).map(h => [h.name, h.value])) }}', - }, - }, - }, + default: false, + description: 'Whether to strip navigation, headers, and footers — return only the main body content', + routing: { request: { body: { useMainContentOnly: '={{ $value }}' } } }, }, { displayName: 'Max Age (Ms)', name: 'maxAgeMs', type: 'number', displayOptions: { - show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, + show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages'] }, }, default: 86400000, description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', @@ -267,6 +330,96 @@ export const scrapingFields: INodeProperties[] = [ typeOptions: { minValue: 1, maxValue: 500 }, routing: { request: { body: { maxPages: '={{ $value }}' } } }, }, + { + displayName: 'PDF Parsing', + name: 'pdf', + type: 'collection', + placeholder: 'Add PDF Option', + default: {}, + displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, + description: 'Control how linked PDF documents are fetched and parsed', + options: [ + { + displayName: 'End Page', + name: 'end', + type: 'number', + default: 1, + description: 'Last 1-based PDF page to parse. Must be greater than or equal to Start Page.', + typeOptions: { minValue: 1 }, + }, + { + displayName: 'Should Parse', + name: 'shouldParse', + type: 'boolean', + default: true, + description: + 'Whether PDF pages are fetched and parsed. When false, PDF pages are skipped.', + }, + { + displayName: 'Start Page', + name: 'start', + type: 'number', + default: 1, + description: 'First 1-based PDF page to parse', + typeOptions: { minValue: 1 }, + }, + ], + routing: { request: { qs: { pdf: '={{ $value }}' } } }, + }, + { + displayName: 'PDF Parsing', + name: 'pdfPost', + type: 'collection', + placeholder: 'Add PDF Option', + default: {}, + displayOptions: { show: { '/operation': ['crawl'] } }, + description: 'Control how linked PDF documents are fetched and parsed', + options: [ + { + displayName: 'End Page', + name: 'end', + type: 'number', + default: 1, + description: 'Last 1-based PDF page to parse. Must be greater than or equal to Start Page.', + typeOptions: { minValue: 1 }, + }, + { + displayName: 'Should Parse', + name: 'shouldParse', + type: 'boolean', + default: true, + description: + 'Whether PDF pages are fetched and parsed. When false, PDF pages are skipped.', + }, + { + displayName: 'Start Page', + name: 'start', + type: 'number', + default: 1, + description: 'First 1-based PDF page to parse', + typeOptions: { minValue: 1 }, + }, + ], + routing: { request: { body: { pdf: '={{ $value }}' } } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'shortenBase64Images', + type: 'boolean', + displayOptions: { show: { '/operation': ['scrapeMd'] } }, + default: false, + description: 'Whether to truncate inline base64 image payloads to keep responses small', + routing: { request: { qs: { shortenBase64Images: '={{ $value }}' } } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'shortenBase64ImagesPost', + type: 'boolean', + displayOptions: { show: { '/operation': ['crawl'] } }, + default: false, + description: 'Whether to truncate inline base64 image payloads to keep responses small', + routing: { request: { body: { shortenBase64Images: '={{ $value }}' } } }, + }, { displayName: 'Stop After (Ms)', name: 'stopAfterMs', @@ -286,9 +439,8 @@ export const scrapingFields: INodeProperties[] = [ show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, }, default: 30000, - description: - 'Maximum time in milliseconds to wait for a response before the request fails.', - typeOptions: { minValue: 1, maxValue: 300000 }, + description: 'Maximum time in milliseconds to wait for a response before the request fails', + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, }, { @@ -297,9 +449,8 @@ export const scrapingFields: INodeProperties[] = [ type: 'number', displayOptions: { show: { '/operation': ['crawl'] } }, default: 30000, - description: - 'Maximum time in milliseconds to wait for a response before the request fails.', - typeOptions: { minValue: 1, maxValue: 300000 }, + description: 'Maximum time in milliseconds to wait for a response before the request fails', + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, }, { diff --git a/nodes/ContextDev/resources/web/searchExtract.ts b/nodes/ContextDev/resources/web/searchExtract.ts index 98c15f6..8113a96 100644 --- a/nodes/ContextDev/resources/web/searchExtract.ts +++ b/nodes/ContextDev/resources/web/searchExtract.ts @@ -43,6 +43,17 @@ export const searchExtractFields: INodeProperties[] = [ default: {}, displayOptions: { show: { ...show, operation: searchExtractOps } }, options: [ + { + displayName: 'Country', + name: 'country', + type: 'string', + displayOptions: { show: { '/operation': ['search'] } }, + default: '', + placeholder: 'us', + description: + 'Two-letter ISO 3166-1 alpha-2 country code to run the search from that location', + routing: { request: { body: { country: '={{ $value }}' } } }, + }, { displayName: 'Exclude Domains', name: 'excludeDomains', @@ -63,7 +74,7 @@ export const searchExtractFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['extract'] } }, default: false, - description: 'Require every returned value to be grounded in facts stated on the page', + description: 'Whether to require every returned value to be grounded in facts stated on the page', routing: { request: { body: { factCheck: '={{ $value }}' } } }, }, { @@ -72,7 +83,7 @@ export const searchExtractFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['extract'] } }, default: false, - description: 'Follow links to subdomains during the extraction crawl', + description: 'Whether to follow links to subdomains during the extraction crawl', routing: { request: { body: { followSubdomains: '={{ $value }}' } } }, }, { @@ -110,7 +121,7 @@ export const searchExtractFields: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { '/operation': ['extract'] } }, default: false, - description: 'Include content from iframes embedded in the page', + description: 'Whether to include content from iframes embedded in the page', routing: { request: { body: { includeFrames: '={{ $value }}' } } }, }, { @@ -123,6 +134,119 @@ export const searchExtractFields: INodeProperties[] = [ 'Optional guidance on which facts to prioritize or how to interpret schema fields', routing: { request: { body: { instructions: '={{ $value }}' } } }, }, + { + displayName: 'Markdown: Include Frames', + name: 'markdownIncludeFrames', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: false, + description: 'Whether to render iframe contents into the Markdown', + routing: { send: { type: 'body', property: 'markdownOptions.includeFrames' } }, + }, + { + displayName: 'Markdown: Include Images', + name: 'markdownIncludeImages', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: false, + description: 'Whether to emit image references in the Markdown', + routing: { send: { type: 'body', property: 'markdownOptions.includeImages' } }, + }, + { + displayName: 'Markdown: Include Links', + name: 'markdownIncludeLinks', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: false, + description: 'Whether to keep hyperlinks in the Markdown', + routing: { send: { type: 'body', property: 'markdownOptions.includeLinks' } }, + }, + { + displayName: 'Markdown: Max Age (Ms)', + name: 'markdownMaxAgeMs', + type: 'number', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: 86400000, + description: + 'Cache TTL in ms for scraped Markdown keyed by URL + options. Default 1 day, max 30 days. Set to 0 to force a fresh scrape.', + routing: { send: { type: 'body', property: 'markdownOptions.maxAgeMs' } }, + }, + { + displayName: 'Markdown: PDF Parsing', + name: 'markdownPdf', + type: 'collection', + placeholder: 'Add PDF Option', + default: {}, + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + description: + 'Control how PDF result pages are fetched and parsed when scraping search results to Markdown', + options: [ + { + displayName: 'End Page', + name: 'end', + type: 'number', + default: 1, + description: 'Last PDF page to parse (1-based, inclusive). Defaults to the final page. Must be >= Start Page.', + typeOptions: { minValue: 1 }, + }, + { + displayName: 'Should Parse', + name: 'shouldParse', + type: 'boolean', + default: true, + description: + 'Whether PDF URLs are parsed. When false, PDF results are skipped with WEBSITE_ACCESS_ERROR.', + }, + { + displayName: 'Start Page', + name: 'start', + type: 'number', + default: 1, + description: 'First PDF page to parse (1-based, inclusive). Defaults to page 1.', + typeOptions: { minValue: 1 }, + }, + ], + routing: { send: { type: 'body', property: 'markdownOptions.pdf' } }, + }, + { + displayName: 'Markdown: Shorten Base64 Images', + name: 'markdownShortenBase64Images', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: false, + description: 'Whether to truncate inline base64 image payloads to keep responses small', + routing: { send: { type: 'body', property: 'markdownOptions.shortenBase64Images' } }, + }, + { + displayName: 'Markdown: Timeout (Ms)', + name: 'markdownTimeoutMS', + type: 'number', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: 30000, + description: + 'Maximum time in milliseconds to wait for each result scrape before it is aborted', + typeOptions: { minValue: 1000, maxValue: 300000 }, + routing: { send: { type: 'body', property: 'markdownOptions.timeoutMS' } }, + }, + { + displayName: 'Markdown: Use Main Content Only', + name: 'markdownUseMainContentOnly', + type: 'boolean', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: false, + description: 'Whether to strip nav, header, footer, and sidebar — keep only the primary article content', + routing: { send: { type: 'body', property: 'markdownOptions.useMainContentOnly' } }, + }, + { + displayName: 'Markdown: Wait For (Ms)', + name: 'markdownWaitForMs', + type: 'number', + displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, + default: 0, + description: 'Extra wait after page load before rendering, in ms. Useful for JS-heavy pages.', + typeOptions: { minValue: 0, maxValue: 30000 }, + routing: { send: { type: 'body', property: 'markdownOptions.waitForMs' } }, + }, { displayName: 'Max Age (Ms)', name: 'maxAgeMs', @@ -153,13 +277,59 @@ export const searchExtractFields: INodeProperties[] = [ typeOptions: { minValue: 1, maxValue: 50 }, routing: { request: { body: { maxPages: '={{ $value }}' } } }, }, + { + displayName: 'Number of Results', + name: 'numResults', + type: 'number', + displayOptions: { show: { '/operation': ['search'] } }, + default: 10, + description: 'Number of results to request and return. Between 10 and 100.', + typeOptions: { minValue: 10, maxValue: 100 }, + routing: { request: { body: { numResults: '={{ $value }}' } } }, + }, + { + displayName: 'PDF Parsing', + name: 'pdf', + type: 'collection', + placeholder: 'Add PDF Option', + default: {}, + displayOptions: { show: { '/operation': ['extract'] } }, + description: 'Control how linked PDF documents are fetched and parsed', + options: [ + { + displayName: 'End Page', + name: 'end', + type: 'number', + default: 1, + description: 'Last 1-based PDF page to parse. Must be greater than or equal to Start Page.', + typeOptions: { minValue: 1 }, + }, + { + displayName: 'Should Parse', + name: 'shouldParse', + type: 'boolean', + default: true, + description: + 'Whether PDF pages are fetched and parsed. When false, PDF pages are skipped.', + }, + { + displayName: 'Start Page', + name: 'start', + type: 'number', + default: 1, + description: 'First 1-based PDF page to parse', + typeOptions: { minValue: 1 }, + }, + ], + routing: { request: { body: { pdf: '={{ $value }}' } } }, + }, { displayName: 'Query Fanout', name: 'queryFanout', type: 'boolean', displayOptions: { show: { '/operation': ['search'] } }, default: false, - description: 'Expand the query into multiple parallel variants for broader recall', + description: 'Whether to expand the query into multiple parallel variants for broader recall', routing: { request: { body: { queryFanout: '={{ $value }}' } } }, }, { @@ -169,7 +339,7 @@ export const searchExtractFields: INodeProperties[] = [ displayOptions: { show: { '/operation': ['search'] } }, default: false, description: - 'Scrape each search result page and include the full content as Markdown. Significantly increases response time.', + 'Whether to scrape each search result page and include the full content as Markdown. Significantly increases response time.', routing: { send: { type: 'body', property: 'markdownOptions.enabled' } }, }, { @@ -188,9 +358,8 @@ export const searchExtractFields: INodeProperties[] = [ name: 'timeoutMS', type: 'number', default: 30000, - description: - 'Maximum time in milliseconds to wait for a response before the request fails.', - typeOptions: { minValue: 1, maxValue: 300000 }, + description: 'Maximum time in milliseconds to wait for a response before the request fails', + typeOptions: { minValue: 1000, maxValue: 300000 }, routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, }, { diff --git a/package.json b/package.json index 9f29590..dbf81ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-context-dev", - "version": "2.0.0", + "version": "3.0.0", "description": "n8n community node for Context.dev API — brand intelligence, web scraping, industry classification, and AI data extraction", "license": "MIT", "homepage": "https://github.com/context-dot-dev/n8n-integration#readme", diff --git a/tests/aiDataExtraction.test.ts b/tests/aiDataExtraction.test.ts index ce50536..6742362 100644 --- a/tests/aiDataExtraction.test.ts +++ b/tests/aiDataExtraction.test.ts @@ -10,6 +10,7 @@ import { showsForOperations, additionalFieldShowsFor, getFixedCollectionValues, + bodyKeyFor, } from './helpers'; describe('aiDataExtraction resource', () => { @@ -61,11 +62,16 @@ describe('aiDataExtraction resource', () => { expect(field?.required).toBe(true); }); - it('domain is required for extractProducts', () => { + it('domain is optional for extractProducts (directUrl is an alternative)', () => { const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => showsForOperations(f).includes('extractProducts'), ); - expect(field?.required).toBe(true); + expect(field?.required).toBeFalsy(); + }); + + it('directUrl is optional for extractProducts (domain is an alternative)', () => { + const field = getTopField(aiDataExtractionDescription, 'directUrl'); + expect(field?.required).toBeFalsy(); }); it('url is required for extractProduct', () => { @@ -82,14 +88,23 @@ describe('aiDataExtraction resource', () => { // ─── displayOptions scoping ──────────────────────────────────────────────── describe('displayOptions scoping', () => { - it('domain shows for aiQuery and extractProducts, not extractProduct', () => { + it('domain (aiQuery variant) shows only for aiQuery', () => { const domainField = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => showsForOperations(f).includes('aiQuery'), )!; - const ops = showsForOperations(domainField); - expect(ops).toContain('aiQuery'); - expect(ops).toContain('extractProducts'); - expect(ops).not.toContain('extractProduct'); + expect(showsForOperations(domainField)).toEqual(['aiQuery']); + }); + + it('domain (extractProducts variant) shows only for extractProducts', () => { + const domainField = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => + showsForOperations(f).includes('extractProducts'), + )!; + expect(showsForOperations(domainField)).toEqual(['extractProducts']); + }); + + it('directUrl shows only for extractProducts', () => { + const field = getTopField(aiDataExtractionDescription, 'directUrl')!; + expect(showsForOperations(field)).toEqual(['extractProducts']); }); it('url shows only for extractProduct', () => { @@ -122,6 +137,12 @@ describe('aiDataExtraction resource', () => { const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; expect(usesBodyRouting(field)).toBe(true); }); + + it('directUrl uses body routing to directUrl', () => { + const field = getTopField(aiDataExtractionDescription, 'directUrl')!; + expect(usesBodyRouting(field)).toBe(true); + expect(bodyKeyFor(field)).toBe('directUrl'); + }); }); // ─── data_to_extract fixedCollection ────────────────────────────────────── @@ -155,6 +176,13 @@ describe('aiDataExtraction resource', () => { expect(values.find((v) => v.name === 'datapoint_list_type')).toBeDefined(); }); + it('datapoint_list_type offers the full SDK enum incl. list + text', () => { + const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); + const lt = values.find((v) => v.name === 'datapoint_list_type')!; + const opts = (lt.options as { value: string }[]).map((o) => o.value).sort(); + expect(opts).toEqual(['boolean', 'date', 'list', 'number', 'object', 'string', 'text', 'url']); + }); + it('has datapoint_name, datapoint_description, datapoint_example fields', () => { const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); const names = values.map((v) => v.name); @@ -162,6 +190,18 @@ describe('aiDataExtraction resource', () => { expect(names).toContain('datapoint_description'); expect(names).toContain('datapoint_example'); }); + + it('has a datapoint_object_schema field of type json (used when list_type is object)', () => { + const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); + const field = values.find((v) => v.name === 'datapoint_object_schema'); + expect(field).toBeDefined(); + expect(field?.type).toBe('json'); + }); + + it('data_to_extract routing still maps into body.data_to_extract', () => { + const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; + expect(bodyKeyFor(field)).toBe('data_to_extract'); + }); }); // ─── additionalFields ────────────────────────────────────────────────────── @@ -197,6 +237,19 @@ describe('aiDataExtraction resource', () => { it('timeoutMS exists', () => { expect(getAdditionalField(aiDataExtractionDescription, 'timeoutMS')).toBeDefined(); }); + + it('maxAgeMs exists, is type number, and routes to body.maxAgeMs', () => { + const field = getAdditionalField(aiDataExtractionDescription, 'maxAgeMs')!; + expect(field).toBeDefined(); + expect(field.type).toBe('number'); + expect(bodyKeyFor(field)).toBe('maxAgeMs'); + }); + + it('maxAgeMs is scoped to extractProduct and extractProducts only', () => { + const field = getAdditionalField(aiDataExtractionDescription, 'maxAgeMs')!; + const ops = additionalFieldShowsFor(field); + expect(ops.sort()).toEqual(['extractProduct', 'extractProducts'].sort()); + }); }); // ─── All fields have defaults ────────────────────────────────────────────── @@ -209,4 +262,34 @@ describe('aiDataExtraction resource', () => { expect(missing.map((p) => p.name)).toEqual([]); }); }); + + describe('specific_pages (aiQuery)', () => { + const field = () => getTopField(aiDataExtractionDescription, 'specificPages')!; + + it('is a collection scoped to aiQuery', () => { + expect(field()).toBeDefined(); + expect(field().type).toBe('collection'); + expect(showsForOperations(field())).toEqual(['aiQuery']); + }); + + it('exposes all 9 SDK page flags, each routing specific_pages.', () => { + const opts = field().options as { name: string; routing: { send: { property: string } } }[]; + const names = opts.map((o) => o.name).sort(); + expect(names).toEqual( + [ + 'about_us', + 'blog', + 'careers', + 'contact_us', + 'faq', + 'home_page', + 'pricing', + 'privacy_policy', + 'terms_and_conditions', + ].sort(), + ); + for (const o of opts) + expect(o.routing.send.property).toBe(`specific_pages.${o.name}`); + }); + }); }); diff --git a/tests/brand.test.ts b/tests/brand.test.ts index ba0b1fe..3a35b1a 100644 --- a/tests/brand.test.ts +++ b/tests/brand.test.ts @@ -173,8 +173,16 @@ describe('brand resource', () => { expect(getAdditionalField(brandDescription, 'force_language')).toBeDefined(); }); - it('transaction-only fields (mcc, city, country_gl) have /operation scoped to identifyFromTransaction', () => { - const txFields = ['mcc', 'city', 'country_gl']; + it('force_language exposes the full SDK language enum (120 values incl. afrikaans/welsh/zulu)', () => { + const f = getAdditionalField(brandDescription, 'force_language')!; + const values = (f.options as { value: string }[]).map((o) => o.value); + expect(values.length).toBe(120); + for (const v of ['afrikaans', 'welsh', 'zulu', 'english', 'latin', 'yoruba']) + expect(values).toContain(v); + }); + + it('transaction-only fields (mcc, city, phone, high_confidence_only) have /operation scoped to identifyFromTransaction', () => { + const txFields = ['mcc', 'city', 'phone', 'high_confidence_only']; for (const name of txFields) { const field = getAdditionalField(brandDescription, name); expect(field).toBeDefined(); @@ -182,11 +190,74 @@ describe('brand resource', () => { } }); + it('country_gl is scoped to identifyFromTransaction and retrieveByName (SDK supports both)', () => { + const field = getAdditionalField(brandDescription, 'country_gl'); + expect(field).toBeDefined(); + const scope = additionalFieldShowsFor(field!); + expect(scope).toEqual(expect.arrayContaining(['identifyFromTransaction', 'retrieveByName'])); + expect(scope).toHaveLength(2); + }); + + it('country_gl is not scoped to operations whose SDK params lack it (retrieve, retrieveByEmail, retrieveByIsin, retrieveByTicker, retrieveSimplified)', () => { + const field = getAdditionalField(brandDescription, 'country_gl')!; + const scope = additionalFieldShowsFor(field); + expect(scope).not.toContain('retrieve'); + expect(scope).not.toContain('retrieveByEmail'); + expect(scope).not.toContain('retrieveByIsin'); + expect(scope).not.toContain('retrieveByTicker'); + expect(scope).not.toContain('retrieveSimplified'); + }); + + it('phone is a string routing qs key "phone"', () => { + const field = getAdditionalField(brandDescription, 'phone')!; + expect(field.type).toBe('string'); + expect(qsKeyFor(field)).toBe('phone'); + }); + + it('high_confidence_only is a boolean routing qs key "high_confidence_only"', () => { + const field = getAdditionalField(brandDescription, 'high_confidence_only')!; + expect(field.type).toBe('boolean'); + expect(qsKeyFor(field)).toBe('high_confidence_only'); + }); + it('ticker_exchange is scoped to retrieveByTicker only', () => { const field = getAdditionalField(brandDescription, 'ticker_exchange'); expect(field).toBeDefined(); expect(additionalFieldShowsFor(field!)).toEqual(['retrieveByTicker']); }); + + it('ticker_exchange contains all 72 SDK exchange codes, defaulting to NASDAQ', () => { + const field = getAdditionalField(brandDescription, 'ticker_exchange')!; + const values = (field.options as { value: string }[]).map((o) => o.value); + const expectedCodes = [ + 'AMEX', 'AMS', 'AQS', 'ASX', 'ATH', 'BER', 'BME', 'BRU', 'BSE', 'BUD', + 'BUE', 'BVC', 'CBOE', 'CNQ', 'CPH', 'DFM', 'DOH', 'DUB', 'DUS', 'DXE', + 'EGX', 'FSX', 'HAM', 'HEL', 'HKSE', 'HOSE', 'ICE', 'IOB', 'IST', 'JKT', + 'JNB', 'JPX', 'KLS', 'KOE', 'KSC', 'KUW', 'LIS', 'LSE', 'MCX', 'MEX', + 'MIL', 'MUN', 'NASDAQ', 'NEO', 'NSE', 'NYSE', 'NZE', 'OSL', 'OTC', 'PAR', + 'PNK', 'PRA', 'RIS', 'SAO', 'SAU', 'SES', 'SET', 'SGO', 'SHH', 'SHZ', + 'SIX', 'STO', 'STU', 'TAI', 'TAL', 'TLV', 'TSX', 'TSXV', 'TWO', 'VIE', + 'WSE', 'XETRA', + ]; + expect(expectedCodes).toHaveLength(72); + expect(values.sort()).toEqual([...expectedCodes].sort()); + expect(field.default).toBe('NASDAQ'); + }); + + it('ticker_exchange does not contain invalid codes SGX or SSE', () => { + const field = getAdditionalField(brandDescription, 'ticker_exchange')!; + const values = (field.options as { value: string }[]).map((o) => o.value); + expect(values).not.toContain('SGX'); + expect(values).not.toContain('SSE'); + }); + + it('ticker_exchange includes representative previously-missing codes', () => { + const field = getAdditionalField(brandDescription, 'ticker_exchange')!; + const values = (field.options as { value: string }[]).map((o) => o.value); + for (const code of ['LIS', 'MIL', 'PAR', 'SIX', 'XETRA', 'OTC', 'TSXV', 'CBOE']) { + expect(values).toContain(code); + } + }); }); // ─── Routing key names match API param names ────────────────────────────── diff --git a/tests/helpers.ts b/tests/helpers.ts index b89220b..9b8fb7f 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -128,6 +128,16 @@ export function arrayFormatFor(field: INodeProperties): string | undefined { return (field.routing?.request as { arrayFormat?: string } | undefined)?.arrayFormat; } +/** Get the routing.send.property path a field routes to (e.g. 'markdownOptions.enabled') */ +export function sendPropertyFor(field: INodeProperties): string | undefined { + return (field.routing?.send as { property?: string } | undefined)?.property; +} + +/** Get the routing.send.type a field uses (e.g. 'body') */ +export function sendTypeFor(field: INodeProperties): string | undefined { + return (field.routing?.send as { type?: string } | undefined)?.type; +} + /** Every INodeProperties in a description, including nested options */ export function allFields(desc: INodeProperties[]): INodeProperties[] { const result: INodeProperties[] = []; diff --git a/tests/monitors.test.ts b/tests/monitors.test.ts new file mode 100644 index 0000000..8095d35 --- /dev/null +++ b/tests/monitors.test.ts @@ -0,0 +1,390 @@ +import type { INodeProperties } from 'n8n-workflow'; +import { monitorsDescription } from '../nodes/ContextDev/resources/monitors'; +import { + getOperations, + operationExists, + urlFor, + methodFor, + getTopField, + getAdditionalField, + showsForOperations, +} from './helpers'; + +// ─── local helpers for send.property routing (monitors builds nested bodies this way) ───────── +const sendOf = (f?: INodeProperties) => + (f?.routing as { send?: { type?: string; property?: string; value?: string } } | undefined)?.send; + +/** find a field by name anywhere: top-level or inside any collection's options */ +const anyField = (name: string): INodeProperties | undefined => + getTopField(monitorsDescription, name) ?? getAdditionalField(monitorsDescription, name); + +describe('monitors resource', () => { + // ─── operations ──────────────────────────────────────────────────────────── + const OPS: [value: string, method: string, urlIncludes: string][] = [ + ['create', 'POST', '/monitors'], + ['get', 'GET', '/monitors/'], + ['update', 'PATCH', '/monitors/'], + ['list', 'GET', '/monitors'], + ['delete', 'DELETE', '/monitors/'], + ['run', 'POST', '/run'], + ['listAccountChanges', 'GET', '/monitors/changes'], + ['listAccountRuns', 'GET', '/monitors/runs'], + ['listChanges', 'GET', '/changes'], + ['listRuns', 'GET', '/runs'], + ['getChange', 'GET', '/monitors/changes/'], + ]; + + describe('operations', () => { + it('has exactly 11 operations', () => { + expect(getOperations(monitorsDescription)).toHaveLength(11); + }); + + it('has no duplicate operation values', () => { + const vals = getOperations(monitorsDescription).map((o) => o.value); + expect(new Set(vals).size).toBe(vals.length); + }); + + it('every operation defines an action (required for usableAsTool)', () => { + for (const o of getOperations(monitorsDescription)) expect(o.action).toBeTruthy(); + }); + + it.each(OPS)('%s → %s and url contains "%s"', (value, method, urlIncludes) => { + expect(operationExists(monitorsDescription, value)).toBe(true); + expect(methodFor(monitorsDescription, value)).toBe(method); + expect(urlFor(monitorsDescription, value)).toContain(urlIncludes); + }); + }); + + // ─── path params ───────────────────────────────────────────────────────────── + describe('path params', () => { + it('monitorId is required and scoped to the id-bearing ops', () => { + const f = getTopField(monitorsDescription, 'monitorId')!; + expect(f).toBeDefined(); + expect(f.required).toBe(true); + expect(showsForOperations(f).sort()).toEqual( + ['delete', 'get', 'listChanges', 'listRuns', 'run', 'update'].sort(), + ); + }); + + it('changeId is required and scoped to getChange', () => { + const f = getTopField(monitorsDescription, 'changeId')!; + expect(f).toBeDefined(); + expect(f.required).toBe(true); + expect(showsForOperations(f)).toEqual(['getChange']); + }); + + it('id-bearing op URLs interpolate the id parameter', () => { + for (const op of ['get', 'update', 'delete', 'run', 'listChanges', 'listRuns']) + expect(urlFor(monitorsDescription, op)).toContain('$parameter["monitorId"]'); + expect(urlFor(monitorsDescription, 'getChange')).toContain('$parameter["changeId"]'); + }); + }); + + // ─── create: discriminators + nested body via send.property ────────────────── + describe('create — required core fields', () => { + it('name is required, scoped to create, sends body "name"', () => { + const f = getTopField(monitorsDescription, 'name')!; + expect(f.required).toBe(true); + expect(showsForOperations(f)).toEqual(['create']); + expect(sendOf(f)).toMatchObject({ type: 'body', property: 'name' }); + }); + + it('targetType is an options discriminator (page/sitemap/extract) → target.type', () => { + const f = getTopField(monitorsDescription, 'targetType')!; + expect(f.type).toBe('options'); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'extract', + 'page', + 'sitemap', + ]); + expect(sendOf(f)).toMatchObject({ type: 'body', property: 'target.type' }); + expect(showsForOperations(f)).toEqual(['create']); + }); + + it('targetUrl is required for create and sends target.url', () => { + const f = getTopField(monitorsDescription, 'targetUrl')!; + expect(f.required).toBe(true); + expect(sendOf(f)).toMatchObject({ type: 'body', property: 'target.url' }); + }); + + it('changeDetectionType is an options discriminator (exact/semantic) → change_detection.type', () => { + const f = getTopField(monitorsDescription, 'changeDetectionType')!; + expect(f.type).toBe('options'); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'exact', + 'semantic', + ]); + expect(sendOf(f)).toMatchObject({ type: 'body', property: 'change_detection.type' }); + }); + + it('schedule frequency + unit send schedule.frequency / schedule.unit', () => { + const freq = getTopField(monitorsDescription, 'scheduleFrequency')!; + const unit = getTopField(monitorsDescription, 'scheduleUnit')!; + expect(freq.type).toBe('number'); + expect(sendOf(freq)).toMatchObject({ type: 'body', property: 'schedule.frequency' }); + expect((unit.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'days', + 'hours', + 'minutes', + ]); + expect(sendOf(unit)).toMatchObject({ type: 'body', property: 'schedule.unit' }); + }); + + it('schedule.type is sent as the constant "interval" via a hidden field', () => { + const f = getTopField(monitorsDescription, 'scheduleType')!; + expect(f.type).toBe('hidden'); + expect(f.default).toBe('interval'); + expect(sendOf(f)).toMatchObject({ type: 'body', property: 'schedule.type' }); + }); + }); + + describe('create — conditional target sub-fields', () => { + it('normalizeWhitespace shows only for target page → target.normalize_whitespace', () => { + const f = getTopField(monitorsDescription, 'normalizeWhitespace')!; + expect(f.type).toBe('boolean'); + expect((f.displayOptions?.show as Record).targetType).toEqual(['page']); + expect(sendOf(f)).toMatchObject({ property: 'target.normalize_whitespace' }); + }); + + it('followSubdomains + instructions + schema show only for target extract', () => { + for (const [name, prop] of [ + ['followSubdomains', 'target.follow_subdomains'], + ['extractPrompt', 'target.instructions'], + ['extractSchema', 'target.schema'], + ] as const) { + const f = getTopField(monitorsDescription, name)!; + expect((f.displayOptions?.show as Record).targetType).toEqual(['extract']); + expect(sendOf(f)!.property).toBe(prop); + } + }); + + it('extractSchema parses JSON before sending', () => { + const f = getTopField(monitorsDescription, 'extractSchema')!; + expect(f.type).toBe('json'); + expect(sendOf(f)!.value).toContain('JSON.parse'); + }); + + it('extract max depth / max pages show only for target extract, as numbers', () => { + for (const [name, prop] of [ + ['extractMaxDepth', 'target.max_depth'], + ['extractMaxPages', 'target.max_pages'], + ] as const) { + const f = getTopField(monitorsDescription, name)!; + expect(f).toBeDefined(); + expect(f.type).toBe('number'); + expect((f.displayOptions?.show as Record).targetType).toEqual(['extract']); + expect(sendOf(f)!.property).toBe(prop); + } + }); + + it('sitemap exclude/include/max_urls show only for target sitemap', () => { + const exclude = getTopField(monitorsDescription, 'sitemapExclude')!; + expect(exclude).toBeDefined(); + expect((exclude.displayOptions?.show as Record).targetType).toEqual([ + 'sitemap', + ]); + expect(sendOf(exclude)!.property).toBe('target.exclude'); + expect(sendOf(exclude)!.value).toContain('split'); + + const include = getTopField(monitorsDescription, 'sitemapInclude')!; + expect(include).toBeDefined(); + expect((include.displayOptions?.show as Record).targetType).toEqual([ + 'sitemap', + ]); + expect(sendOf(include)!.property).toBe('target.include'); + expect(sendOf(include)!.value).toContain('split'); + + const maxUrls = getTopField(monitorsDescription, 'sitemapMaxUrls')!; + expect(maxUrls).toBeDefined(); + expect(maxUrls.type).toBe('number'); + expect((maxUrls.displayOptions?.show as Record).targetType).toEqual([ + 'sitemap', + ]); + expect(sendOf(maxUrls)!.property).toBe('target.max_urls'); + }); + }); + + describe('create — conditional semantic fields', () => { + it('semanticQuery shows only for semantic detection → change_detection.query, required', () => { + const f = getTopField(monitorsDescription, 'semanticQuery')!; + expect((f.displayOptions?.show as Record).changeDetectionType).toEqual([ + 'semantic', + ]); + expect(f.required).toBe(true); + expect(sendOf(f)!.property).toBe('change_detection.query'); + }); + + it('confidenceThreshold shows only for semantic → change_detection.confidence_threshold', () => { + const f = getTopField(monitorsDescription, 'confidenceThreshold')!; + expect(f.type).toBe('number'); + expect((f.displayOptions?.show as Record).changeDetectionType).toEqual([ + 'semantic', + ]); + expect(sendOf(f)!.property).toBe('change_detection.confidence_threshold'); + }); + }); + + describe('create — optional webhook + tags', () => { + it('webhookUrl sends webhook.url', () => { + const f = getAdditionalField(monitorsDescription, 'webhookUrl')!; + expect(f).toBeDefined(); + expect(sendOf(f)!.property).toBe('webhook.url'); + }); + + it('webhookSecret (create) sends webhook.secret', () => { + const f = getAdditionalField(monitorsDescription, 'webhookSecret')!; + expect(f).toBeDefined(); + expect(sendOf(f)!.property).toBe('webhook.secret'); + }); + + it('tags splits csv into a string array on body key "tags"', () => { + const f = getAdditionalField(monitorsDescription, 'tags')!; + expect(f).toBeDefined(); + const body = f.routing?.request?.body as Record | undefined; + const send = sendOf(f); + const expr = body?.tags ?? send?.value ?? ''; + expect(expr).toContain('split'); + }); + }); + + // ─── update: all-optional mutable fields ───────────────────────────────────── + describe('update fields', () => { + it('status is options active/paused → body "status"', () => { + const f = anyField('updStatus')!; + expect(f).toBeDefined(); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'active', + 'paused', + ]); + expect(sendOf(f)!.property).toBe('status'); + }); + + it('update fields collection is scoped to the update operation only', () => { + const coll = monitorsDescription.find( + (p) => p.name === 'additionalFields' && showsForOperations(p).includes('update'), + )!; + expect(showsForOperations(coll)).toEqual(['update']); + }); + + it.each([ + ['updName', 'name'], + ['updWebhookUrl', 'webhook.url'], + ['updWebhookSecret', 'webhook.secret'], + ['updQuery', 'change_detection.query'], + ['updExtractInstructions', 'target.instructions'], + ['updExtractSchema', 'target.schema'], + ])('update field %s sends body path %s', (name, prop) => { + const f = anyField(name)!; + expect(f).toBeDefined(); + expect(sendOf(f)!.property).toBe(prop); + }); + + it('update extract schema is JSON and parses before sending', () => { + const f = anyField('updExtractSchema')!; + expect(f.type).toBe('json'); + expect(sendOf(f)!.value).toContain('JSON.parse'); + }); + + it('update schedule is a fixedCollection that always injects the required schedule.type', () => { + const f = anyField('updSchedule')!; + expect(f).toBeDefined(); + expect(f.type).toBe('fixedCollection'); + const expr = (f.routing?.request?.body as Record).schedule; + expect(expr).toContain('type: "interval"'); + expect(expr).toContain('frequency'); + expect(expr).toContain('unit'); + }); + + it('updWebhookRemove clears the webhook by sending null', () => { + const f = anyField('updWebhookRemove')!; + expect(f).toBeDefined(); + expect(f.type).toBe('boolean'); + expect((f.routing?.request?.body as Record).webhook).toContain('null'); + }); + + it('updChangeDetectionType is an options discriminator (exact/semantic) → change_detection.type', () => { + const f = anyField('updChangeDetectionType')!; + expect(f).toBeDefined(); + expect(f.type).toBe('options'); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'exact', + 'semantic', + ]); + expect(sendOf(f)!.property).toBe('change_detection.type'); + }); + + it('updTargetType is an options discriminator (page/sitemap/extract) → target.type', () => { + const f = anyField('updTargetType')!; + expect(f).toBeDefined(); + expect(f.type).toBe('options'); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'extract', + 'page', + 'sitemap', + ]); + expect(sendOf(f)!.property).toBe('target.type'); + }); + + it.each([ + ['updSitemapMaxUrls', 'target.max_urls'], + ['updExtractMaxDepth', 'target.max_depth'], + ['updExtractMaxPages', 'target.max_pages'], + ['updExtractInstructions', 'target.instructions'], + ])('update parity field %s sends body path %s', (name, prop) => { + const f = anyField(name)!; + expect(f).toBeDefined(); + expect(sendOf(f)!.property).toBe(prop); + }); + + it.each([ + ['updSitemapExclude', 'target.exclude'], + ['updSitemapInclude', 'target.include'], + ])('update parity field %s splits csv into %s', (name, prop) => { + const f = anyField(name)!; + expect(f).toBeDefined(); + expect(sendOf(f)!.property).toBe(prop); + expect(sendOf(f)!.value).toContain('split'); + }); + }); + + // ─── list / changes / runs filters + pagination ────────────────────────────── + describe('list filters', () => { + it.each([ + ['status', ['active', 'failed', 'paused']], + ['target_type', ['extract', 'page', 'sitemap']], + ['change_detection_type', ['exact', 'semantic']], + ])('list %s filter is an options field with the SDK enum', (key, values) => { + const f = getAdditionalField(monitorsDescription, key)!; + expect(f).toBeDefined(); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual(values); + expect((f.routing?.request?.qs as Record)[key]).toBeDefined(); + }); + + it('run-status filters use the run enum queued/running/completed/failed', () => { + const f = anyField('runStatus')!; + expect(f).toBeDefined(); + expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ + 'completed', + 'failed', + 'queued', + 'running', + ]); + }); + + it('cursor + limit pagination exist and route to qs', () => { + for (const key of ['cursor', 'limit']) { + const f = getAdditionalField(monitorsDescription, key)!; + expect(f).toBeDefined(); + expect((f.routing?.request?.qs as Record)[key]).toBeDefined(); + } + }); + + it('since/until/tag/monitor_id filters route to qs', () => { + for (const key of ['since', 'until', 'tag', 'monitor_id']) { + const f = getAdditionalField(monitorsDescription, key)!; + expect(f).toBeDefined(); + expect((f.routing?.request?.qs as Record)[key]).toBeDefined(); + } + }); + }); +}); diff --git a/tests/node.test.ts b/tests/node.test.ts index 7050c26..499ca8a 100644 --- a/tests/node.test.ts +++ b/tests/node.test.ts @@ -131,8 +131,8 @@ describe('ContextDev node', () => { expect(resourceProp().noDataExpression).toBe(true); }); - it('has exactly 5 resources', () => { - expect(resourceValues()).toHaveLength(5); + it('has exactly 6 resources', () => { + expect(resourceValues()).toHaveLength(6); }); it('includes brand', () => expect(resourceValues()).toContain('brand')); @@ -140,6 +140,7 @@ describe('ContextDev node', () => { it('includes aiDataExtraction', () => expect(resourceValues()).toContain('aiDataExtraction')); it('includes industry', () => expect(resourceValues()).toContain('industry')); it('includes utility (new)', () => expect(resourceValues()).toContain('utility')); + it('includes monitors (new)', () => expect(resourceValues()).toContain('monitors')); it('does NOT include screenshot (merged into web)', () => { expect(resourceValues()).not.toContain('screenshot'); diff --git a/tests/web.test.ts b/tests/web.test.ts index 7dc8c0a..96ebc79 100644 --- a/tests/web.test.ts +++ b/tests/web.test.ts @@ -16,6 +16,8 @@ import { bodyKeyFor, arrayFormatFor, getAdditionalFieldFor, + sendPropertyFor, + sendTypeFor, } from './helpers'; describe('web resource', () => { @@ -321,12 +323,19 @@ describe('web resource', () => { expect(additionalFieldShowsFor(field)).toEqual(['search']); }); - it('directUrl is scoped to screenshot, extractStyleguide, extractFonts', () => { + it('directUrl is scoped to screenshot, styleguide, fonts (NOT competitors — SDK has no directUrl there)', () => { const field = getAdditionalField(webDescription, 'directUrl')!; const ops = additionalFieldShowsFor(field); - expect(ops).toContain('screenshot'); - expect(ops).toContain('extractStyleguide'); - expect(ops).toContain('extractFonts'); + expect(ops.sort()).toEqual(['extractFonts', 'extractStyleguide', 'screenshot']); + expect(ops).not.toContain('extractCompetitors'); + }); + + it('visual-op domain is NOT required and omits empty values (enables directUrl-only)', () => { + const domain = getAllTopFields(webDescription, 'domain').find((f) => + showsForOperations(f).includes('screenshot'), + )!; + expect(domain.required).not.toBe(true); + expect((domain.routing?.request?.qs as Record).domain).toContain('|| undefined'); }); }); @@ -490,4 +499,223 @@ describe('web resource', () => { expect((f!.typeOptions as { maxValue?: number }).maxValue).toBe(500); }); }); + + // ─── nested + flat param additions (SDK sync) ─────────────────────────────── + const subNames = (f?: { options?: unknown }) => + ((f?.options as { name: string }[] | undefined) ?? []).map((o) => o.name).sort(); + + describe('pdf nested collection', () => { + it('GET pdf (scrapeMd/scrapeHtml) is a collection routing qs key "pdf"', () => { + const f = getAdditionalFieldFor(webDescription, 'pdf', 'scrapeMd')!; + expect(f.type).toBe('collection'); + expect(additionalFieldShowsFor(f)).toEqual(['scrapeMd', 'scrapeHtml']); + expect(qsKeyFor(f)).toBe('pdf'); + expect(subNames(f)).toEqual(['end', 'shouldParse', 'start']); + }); + + it('crawl pdf routes to body key "pdf", scoped to crawl only', () => { + const f = getAdditionalFieldFor(webDescription, 'pdfPost', 'crawl')!; + expect(f.type).toBe('collection'); + expect(bodyKeyFor(f)).toBe('pdf'); + expect(additionalFieldShowsFor(f)).toEqual(['crawl']); + expect(subNames(f)).toEqual(['end', 'shouldParse', 'start']); + }); + + it('extract pdf routes to body key "pdf", scoped to extract, with all sub-fields', () => { + const f = getAdditionalFieldFor(webDescription, 'pdf', 'extract')!; + expect(f.type).toBe('collection'); + expect(bodyKeyFor(f)).toBe('pdf'); + expect(additionalFieldShowsFor(f)).toEqual(['extract']); + expect(subNames(f)).toEqual(['end', 'shouldParse', 'start']); + }); + }); + + describe('enrichment nested collection (scrapeImages)', () => { + it('is a collection scoped to scrapeImages routing qs key "enrichment"', () => { + const f = getAdditionalField(webDescription, 'enrichment')!; + expect(f.type).toBe('collection'); + expect(additionalFieldShowsFor(f)).toEqual(['scrapeImages']); + expect(qsKeyFor(f)).toBe('enrichment'); + expect(subNames(f)).toEqual(['classification', 'hostedUrl', 'maxTimePerMs', 'resolution']); + }); + }); + + describe('shortenBase64Images', () => { + it('GET variant (scrapeMd) routes to qs, scoped to scrapeMd only', () => { + const f = getAdditionalFieldFor(webDescription, 'shortenBase64Images', 'scrapeMd')!; + expect(f.type).toBe('boolean'); + expect(qsKeyFor(f)).toBe('shortenBase64Images'); + expect(additionalFieldShowsFor(f)).toEqual(['scrapeMd']); + }); + + it('POST variant (crawl) routes to body, scoped to crawl only', () => { + const f = getAdditionalFieldFor(webDescription, 'shortenBase64ImagesPost', 'crawl')!; + expect(bodyKeyFor(f)).toBe('shortenBase64Images'); + expect(additionalFieldShowsFor(f)).toEqual(['crawl']); + }); + }); + + describe('viewport nested collection (screenshot)', () => { + it('is a collection scoped to screenshot routing qs key "viewport"', () => { + const f = getAdditionalField(webDescription, 'viewport')!; + expect(f.type).toBe('collection'); + expect(additionalFieldShowsFor(f)).toEqual(['screenshot']); + expect(qsKeyFor(f)).toBe('viewport'); + expect(subNames(f)).toEqual(['height', 'width']); + }); + }); + + // ─── SDK 1.41 web params (colorScheme, country, numResults, scrollOffset) ──── + describe('colorScheme (SDK 1.41)', () => { + it('is an options field offering light + dark', () => { + const f = getAdditionalField(webDescription, 'colorScheme')!; + expect(f).toBeDefined(); + expect(f.type).toBe('options'); + const values = (f.options as { value: string }[]).map((o) => o.value); + expect(values).toContain('light'); + expect(values).toContain('dark'); + }); + + it('is scoped to screenshot + extractStyleguide and routes to qs key "colorScheme"', () => { + const f = getAdditionalField(webDescription, 'colorScheme')!; + expect(additionalFieldShowsFor(f).sort()).toEqual(['extractStyleguide', 'screenshot']); + expect(qsKeyFor(f)).toBe('colorScheme'); + }); + + it('defaults to "" (Auto)', () => { + expect(getAdditionalField(webDescription, 'colorScheme')!.default).toBe(''); + }); + }); + + describe('scrollOffset (SDK 1.41)', () => { + it('is a number scoped to screenshot, routing qs key "scrollOffset"', () => { + const f = getAdditionalField(webDescription, 'scrollOffset')!; + expect(f).toBeDefined(); + expect(f.type).toBe('number'); + expect(additionalFieldShowsFor(f)).toEqual(['screenshot']); + expect(qsKeyFor(f)).toBe('scrollOffset'); + }); + + it('bounds match SDK (0..100000, default 0)', () => { + const f = getAdditionalField(webDescription, 'scrollOffset')!; + expect(f.default).toBe(0); + expect((f.typeOptions as { minValue?: number; maxValue?: number }).minValue).toBe(0); + expect((f.typeOptions as { minValue?: number; maxValue?: number }).maxValue).toBe(100000); + }); + }); + + describe('numResults (SDK 1.41)', () => { + it('is a number scoped to search, routing body key "numResults"', () => { + const f = getAdditionalField(webDescription, 'numResults')!; + expect(f).toBeDefined(); + expect(f.type).toBe('number'); + expect(additionalFieldShowsFor(f)).toEqual(['search']); + expect(bodyKeyFor(f)).toBe('numResults'); + }); + + it('bounds match SDK (10..100, default 10)', () => { + const f = getAdditionalField(webDescription, 'numResults')!; + expect(f.default).toBe(10); + expect((f.typeOptions as { minValue?: number; maxValue?: number }).minValue).toBe(10); + expect((f.typeOptions as { minValue?: number; maxValue?: number }).maxValue).toBe(100); + }); + }); + + describe('country (SDK 1.41)', () => { + // exact op×routing matrix from the SDK: qs for GET ops, body for POST ops. + // The two GET scrape ops share one qs field; screenshot/crawl/search each get their own. + const cases: [op: string, fieldName: string, loc: 'qs' | 'body', show: string[]][] = [ + ['scrapeMd', 'country', 'qs', ['scrapeMd', 'scrapeHtml']], + ['scrapeHtml', 'country', 'qs', ['scrapeMd', 'scrapeHtml']], + ['screenshot', 'country', 'qs', ['screenshot']], + ['crawl', 'countryPost', 'body', ['crawl']], + ['search', 'country', 'body', ['search']], + ]; + + it.each(cases)('%s: string field routes country via %s with exact scoping', (op, name, loc, show) => { + const f = getAdditionalFieldFor(webDescription, name, op)!; + expect(f).toBeDefined(); + expect(f.type).toBe('string'); + expect(f.default).toBe(''); + expect(additionalFieldShowsFor(f).sort()).toEqual([...show].sort()); + if (loc === 'qs') expect(qsKeyFor(f)).toBe('country'); + else expect(bodyKeyFor(f)).toBe('country'); + }); + + it('country is NOT offered for extractStyleguide (SDK has no country there)', () => { + expect(getAdditionalFieldFor(webDescription, 'country', 'extractStyleguide')).toBeUndefined(); + expect(getAdditionalFieldFor(webDescription, 'countryPost', 'extractStyleguide')).toBeUndefined(); + }); + }); + + // ─── search markdownOptions sub-fields (capability drop fix) ──────────────── + // markdownEnabled only ever sent markdownOptions.enabled — once a user turned + // scraping on for search results, there was no way to configure it. These + // fields expose the rest of WebSearchParams.MarkdownOptions from the SDK. + + describe('search markdownOptions sub-fields', () => { + const booleanCases: [name: string, apiPath: string][] = [ + ['markdownIncludeFrames', 'markdownOptions.includeFrames'], + ['markdownIncludeImages', 'markdownOptions.includeImages'], + ['markdownIncludeLinks', 'markdownOptions.includeLinks'], + ['markdownUseMainContentOnly', 'markdownOptions.useMainContentOnly'], + ['markdownShortenBase64Images', 'markdownOptions.shortenBase64Images'], + ]; + + it.each(booleanCases)('%s is a boolean scoped to search, sending body via "%s"', (name, apiPath) => { + const f = getAdditionalField(webDescription, name); + expect(f).toBeDefined(); + expect(f!.type).toBe('boolean'); + expect(additionalFieldShowsFor(f!)).toEqual(['search']); + expect(sendTypeFor(f!)).toBe('body'); + expect(sendPropertyFor(f!)).toBe(apiPath); + }); + + const numberCases: [name: string, apiPath: string][] = [ + ['markdownMaxAgeMs', 'markdownOptions.maxAgeMs'], + ['markdownTimeoutMS', 'markdownOptions.timeoutMS'], + ['markdownWaitForMs', 'markdownOptions.waitForMs'], + ]; + + it.each(numberCases)('%s is a number scoped to search, sending body via "%s"', (name, apiPath) => { + const f = getAdditionalField(webDescription, name); + expect(f).toBeDefined(); + expect(f!.type).toBe('number'); + expect(additionalFieldShowsFor(f!)).toEqual(['search']); + expect(sendTypeFor(f!)).toBe('body'); + expect(sendPropertyFor(f!)).toBe(apiPath); + }); + + it('all markdownOptions sub-fields only display when markdownEnabled is true', () => { + const names = [...booleanCases, ...numberCases].map(([name]) => name); + for (const name of names) { + const f = getAdditionalField(webDescription, name)!; + const show = f.displayOptions?.show as Record | undefined; + expect(show?.['/markdownEnabled']).toEqual([true]); + } + }); + + describe('markdownPdf nested collection', () => { + it('is a collection scoped to search, sending body via "markdownOptions.pdf"', () => { + const f = getAdditionalField(webDescription, 'markdownPdf'); + expect(f).toBeDefined(); + expect(f!.type).toBe('collection'); + expect(additionalFieldShowsFor(f!)).toEqual(['search']); + expect(sendTypeFor(f!)).toBe('body'); + expect(sendPropertyFor(f!)).toBe('markdownOptions.pdf'); + }); + + it('exposes start, end, shouldParse sub-fields (matches SDK MarkdownOptions.Pdf)', () => { + const f = getAdditionalField(webDescription, 'markdownPdf')!; + const names = (f.options as { name: string }[]).map((o) => o.name).sort(); + expect(names).toEqual(['end', 'shouldParse', 'start']); + }); + + it('only displays when markdownEnabled is true', () => { + const f = getAdditionalField(webDescription, 'markdownPdf')!; + const show = f.displayOptions?.show as Record | undefined; + expect(show?.['/markdownEnabled']).toEqual([true]); + }); + }); + }); }); From f3967d9d6aaeb0c41f82f0a001a92c6f7ced9476 Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Thu, 20 Aug 2026 04:45:14 +0530 Subject: [PATCH 07/10] =?UTF-8?q?Expand=20to=20full=20Context.dev=20API=20?= =?UTF-8?q?coverage=20=E2=80=94=2053=20operations,=209=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns every operation, parameter, enum and numeric bound to the current API, and adds the resources the node did not cover. New resources: Batch (6 ops), People (2), News, Parsing. Added to existing resources: POST /brand/retrieve, GET /brand/search, monitor credit-usage, monitor limits, retrieve a monitor run, and webhook secret rotation. Also adds `tags` across the operations that accept it, plus `zdr`, `settleAnimations`, `actions`, `includeHTML` and `dedupe` on the scrape operations, and raw-binary upload support for POST /parse via a preSend hook. BREAKING: operation values and resource names change (aiDataExtraction -> webExtraction; NAICS/SIC move under Web Extraction). Existing workflows need reconnecting. The credential name `contextdevApi` is unchanged, so saved credentials keep working. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018eb9URz1xqidX41rxa4GPZ --- credentials/ContextdevApi.credentials.ts | 23 +- nodes/ContextDev/ContextDev.node.json | 39 +- nodes/ContextDev/ContextDev.node.ts | 57 +- .../{context-dev-logo.svg => contextDev.svg} | 0 .../resources/aiDataExtraction/index.ts | 317 ----- nodes/ContextDev/resources/batch/index.ts | 392 +++++ nodes/ContextDev/resources/brand/index.ts | 480 ------- .../resources/brandIntelligence/index.ts | 745 ++++++++++ nodes/ContextDev/resources/industry/index.ts | 89 -- nodes/ContextDev/resources/monitors/index.ts | 1268 +++++++++-------- nodes/ContextDev/resources/news/index.ts | 179 +++ nodes/ContextDev/resources/parsing/index.ts | 157 ++ nodes/ContextDev/resources/people/index.ts | 149 ++ nodes/ContextDev/resources/utility/index.ts | 151 +- nodes/ContextDev/resources/web/brand.ts | 182 --- nodes/ContextDev/resources/web/index.ts | 111 -- nodes/ContextDev/resources/web/scraping.ts | 500 ------- .../ContextDev/resources/web/searchExtract.ts | 377 ----- .../resources/webExtraction/index.ts | 687 +++++++++ .../ContextDev/resources/webScraping/index.ts | 1160 +++++++++++++++ 20 files changed, 4347 insertions(+), 2716 deletions(-) rename nodes/ContextDev/{context-dev-logo.svg => contextDev.svg} (100%) delete mode 100644 nodes/ContextDev/resources/aiDataExtraction/index.ts create mode 100644 nodes/ContextDev/resources/batch/index.ts delete mode 100644 nodes/ContextDev/resources/brand/index.ts create mode 100644 nodes/ContextDev/resources/brandIntelligence/index.ts delete mode 100644 nodes/ContextDev/resources/industry/index.ts create mode 100644 nodes/ContextDev/resources/news/index.ts create mode 100644 nodes/ContextDev/resources/parsing/index.ts create mode 100644 nodes/ContextDev/resources/people/index.ts delete mode 100644 nodes/ContextDev/resources/web/brand.ts delete mode 100644 nodes/ContextDev/resources/web/index.ts delete mode 100644 nodes/ContextDev/resources/web/scraping.ts delete mode 100644 nodes/ContextDev/resources/web/searchExtract.ts create mode 100644 nodes/ContextDev/resources/webExtraction/index.ts create mode 100644 nodes/ContextDev/resources/webScraping/index.ts diff --git a/credentials/ContextdevApi.credentials.ts b/credentials/ContextdevApi.credentials.ts index 06d073b..bf3b62e 100644 --- a/credentials/ContextdevApi.credentials.ts +++ b/credentials/ContextdevApi.credentials.ts @@ -1,30 +1,19 @@ -import type { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; +import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow'; export class ContextdevApi implements ICredentialType { name = 'contextdevApi'; displayName = 'Context.dev API'; - icon = 'file:contextdev.svg' as const; + icon = 'file:contextDev.svg' as const; documentationUrl = 'https://docs.context.dev/guides/get-started/quickstart'; properties: INodeProperties[] = [ - { - displayName: 'API Key', - name: 'apiKey', - type: 'string', - typeOptions: { password: true }, - default: '', - }, + { displayName: 'API Key', name: 'apiKey', type: 'string', typeOptions: { password: true }, default: '' }, ]; authenticate: IAuthenticateGeneric = { type: 'generic', properties: { headers: { - Authorization: '=Bearer {{$credentials.apiKey}}', - integration_name: 'n8n', + "Authorization": '=Bearer {{$credentials.apiKey}}', + "integration_name": 'n8n', }, }, }; @@ -32,7 +21,7 @@ export class ContextdevApi implements ICredentialType { request: { baseURL: 'https://api.context.dev/v1', url: '/brand/retrieve', - qs: { domain: 'context.dev' }, + qs: {"domain":"context.dev"}, }, }; } diff --git a/nodes/ContextDev/ContextDev.node.json b/nodes/ContextDev/ContextDev.node.json index bf79709..d12bd9b 100644 --- a/nodes/ContextDev/ContextDev.node.json +++ b/nodes/ContextDev/ContextDev.node.json @@ -1,17 +1,26 @@ { - "node": "n8n-nodes-context-dev.contextDev", - "nodeVersion": "1.0", - "codexVersion": "1.0", - "categories": ["Data & Storage"], - "subcategories": { - "Data & Storage": ["Content & Files"] - }, - "resources": { - "primaryDocumentation": [ - { - "url": "https://docs.context.dev" - } - ] - }, - "alias": ["context.dev", "brand intelligence", "web scraping", "scrape"] + "node": "n8n-nodes-context-dev.contextDev", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Data & Storage" + ], + "subcategories": { + "Data & Storage": [ + "Content & Files" + ] + }, + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.context.dev" + } + ] + }, + "alias": [ + "context.dev", + "brand intelligence", + "web scraping", + "scrape" + ] } diff --git a/nodes/ContextDev/ContextDev.node.ts b/nodes/ContextDev/ContextDev.node.ts index a6840e4..5317065 100644 --- a/nodes/ContextDev/ContextDev.node.ts +++ b/nodes/ContextDev/ContextDev.node.ts @@ -1,35 +1,32 @@ import { NodeConnectionTypes, type INodeType, type INodeTypeDescription } from 'n8n-workflow'; -import { brandDescription } from './resources/brand'; -import { webDescription } from './resources/web'; -import { aiDataExtractionDescription } from './resources/aiDataExtraction'; -import { industryDescription } from './resources/industry'; +import { newsDescription } from './resources/news'; +import { parsingDescription } from './resources/parsing'; +import { webScrapingDescription } from './resources/webScraping'; +import { webExtractionDescription } from './resources/webExtraction'; +import { peopleDescription } from './resources/people'; +import { brandIntelligenceDescription } from './resources/brandIntelligence'; import { utilityDescription } from './resources/utility'; import { monitorsDescription } from './resources/monitors'; +import { batchDescription } from './resources/batch'; export class ContextDev implements INodeType { description: INodeTypeDescription = { displayName: 'Context.dev', name: 'contextDev', - icon: 'file:context-dev-logo.svg', + icon: 'file:contextDev.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', - description: 'Web scraping, brand intelligence, and AI data extraction via Context.dev', - defaults: { - name: 'Context.dev', - }, + description: 'Context.dev API', + defaults: { name: 'Context.dev' }, usableAsTool: true, inputs: [NodeConnectionTypes.Main], outputs: [NodeConnectionTypes.Main], credentials: [{ name: 'contextdevApi', required: true }], requestDefaults: { baseURL: 'https://api.context.dev/v1', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - integration_name: 'n8n', - }, + headers: { Accept: 'application/json', 'Content-Type': 'application/json', "integration_name": 'n8n' }, }, properties: [ { @@ -38,21 +35,27 @@ export class ContextDev implements INodeType { type: 'options', noDataExpression: true, options: [ - { name: 'AI Data Extraction', value: 'aiDataExtraction' }, - { name: 'Brand Intelligence', value: 'brand' }, - { name: 'Industry Classification', value: 'industry' }, - { name: 'Monitor', value: 'monitors' }, - { name: 'Utility', value: 'utility' }, - { name: 'Web Scraping', value: 'web' }, + { name: 'Batch', value: 'batch' }, + { name: 'Brand Intelligence', value: 'brandIntelligence' }, + { name: 'Monitor', value: 'monitors' }, + { name: 'News', value: 'news' }, + { name: 'Parsing', value: 'parsing' }, + { name: 'Person', value: 'people' }, + { name: 'Utility', value: 'utility' }, + { name: 'Web Extraction', value: 'webExtraction' }, + { name: 'Web Scraping', value: 'webScraping' }, ], - default: 'brand', + default: 'news', }, - ...brandDescription, - ...webDescription, - ...aiDataExtractionDescription, - ...industryDescription, - ...utilityDescription, - ...monitorsDescription, + ...newsDescription, + ...parsingDescription, + ...webScrapingDescription, + ...webExtractionDescription, + ...peopleDescription, + ...brandIntelligenceDescription, + ...utilityDescription, + ...monitorsDescription, + ...batchDescription, ], }; } diff --git a/nodes/ContextDev/context-dev-logo.svg b/nodes/ContextDev/contextDev.svg similarity index 100% rename from nodes/ContextDev/context-dev-logo.svg rename to nodes/ContextDev/contextDev.svg diff --git a/nodes/ContextDev/resources/aiDataExtraction/index.ts b/nodes/ContextDev/resources/aiDataExtraction/index.ts deleted file mode 100644 index b692bf2..0000000 --- a/nodes/ContextDev/resources/aiDataExtraction/index.ts +++ /dev/null @@ -1,317 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const show = { resource: ['aiDataExtraction'] }; - -export const aiDataExtractionDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show }, - options: [ - { - name: 'AI Query', - value: 'aiQuery', - action: 'Query a website with AI and extract structured data', - description: - 'Define data points to extract from any domain. View docs.', - routing: { request: { method: 'POST', url: '/brand/ai/query' } }, - }, - { - name: 'Extract Products', - value: 'extractProducts', - action: 'Extract products from a brand website', - description: - 'Extract product catalog with name, description, pricing, features. View docs.', - routing: { request: { method: 'POST', url: '/brand/ai/products' } }, - }, - { - name: 'Extract Single Product', - value: 'extractProduct', - action: 'Extract a single product from a URL', - description: 'Extract product data from a specific product page. View docs.', - routing: { request: { method: 'POST', url: '/brand/ai/product' } }, - }, - ], - default: 'aiQuery', - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['aiQuery'] } }, - default: '', - placeholder: 'stripe.com', - routing: { request: { body: { domain: '={{ $value }}' } } }, - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - displayOptions: { show: { ...show, operation: ['extractProducts'] } }, - default: '', - placeholder: 'stripe.com', - description: 'The domain to crawl for products. Provide either this or Direct URL.', - routing: { request: { body: { domain: '={{ $value || undefined }}' } } }, - }, - { - displayName: 'Direct URL', - name: 'directUrl', - type: 'string', - displayOptions: { show: { ...show, operation: ['extractProducts'] } }, - default: '', - placeholder: 'https://example.com/shop', - description: - 'A specific URL to use directly as the starting point for extraction, without domain resolution. Provide either this or Domain.', - routing: { request: { body: { directUrl: '={{ $value || undefined }}' } } }, - }, - { - displayName: 'Product URL', - name: 'url', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['extractProduct'] } }, - default: '', - placeholder: 'https://example.com/products/widget', - description: 'Direct URL to the product page', - routing: { request: { body: { url: '={{ $value }}' } } }, - }, - { - displayName: 'Data Points to Extract', - name: 'data_to_extract', - type: 'fixedCollection', - required: true, - typeOptions: { multipleValues: true }, - displayOptions: { show: { ...show, operation: ['aiQuery'] } }, - default: {}, - description: 'Define the structured data fields you want to extract from the website', - options: [ - { - name: 'datapoint', - displayName: 'Data Point', - values: [ - { - displayName: 'Description', - name: 'datapoint_description', - type: 'string', - default: '', - placeholder: 'The main tagline shown on the homepage', - }, - { - displayName: 'Example Value', - name: 'datapoint_example', - type: 'string', - default: '', - placeholder: 'The platform for modern teams', - }, - { - displayName: 'List Item Type', - name: 'datapoint_list_type', - type: 'options', - default: 'string', - options: [ - { name: 'Boolean', value: 'boolean' }, - { name: 'Date', value: 'date' }, - { name: 'List', value: 'list' }, - { name: 'Number', value: 'number' }, - { name: 'Object', value: 'object' }, - { name: 'String', value: 'string' }, - { name: 'Text', value: 'text' }, - { name: 'URL', value: 'url' }, - ] - }, - { - displayName: 'Name', - name: 'datapoint_name', - type: 'string', - default: '', - placeholder: 'company_tagline', - }, - { - displayName: 'Object Schema (JSON)', - name: 'datapoint_object_schema', - type: 'json', - default: '', - placeholder: '{ "price": "number", "currency": "string" }', - description: - "Used only when List Item Type is 'Object'. A JSON map of field name to scalar type ('string', 'number', 'date', or 'boolean') describing the shape of each object to extract.", - }, - { - displayName: 'Type', - name: 'datapoint_type', - type: 'options', - default: 'text', - options: [ - { name: 'Boolean', value: 'boolean' }, - { name: 'Date', value: 'date' }, - { name: 'List', value: 'list' }, - { name: 'Number', value: 'number' }, - { name: 'Text', value: 'text' }, - { name: 'URL', value: 'url' }, - ] - }, - ], - }, - ], - routing: { - request: { - body: { - data_to_extract: - "={{ ($value.datapoint || []).map((dp) => ({ ...dp, datapoint_object_schema: dp.datapoint_object_schema ? JSON.parse(dp.datapoint_object_schema) : undefined })) }}", - }, - }, - }, - }, - { - displayName: 'Specific Pages', - name: 'specificPages', - type: 'collection', - placeholder: 'Add Page', - default: {}, - displayOptions: { show: { ...show, operation: ['aiQuery'] } }, - description: 'Restrict the analysis to specific page types', - options: [ - { - displayName: 'About Us', - name: 'about_us', - type: 'boolean', - default: false, - description: 'Whether to analyze the about us page', - routing: { send: { type: 'body', property: 'specific_pages.about_us' } }, - }, - { - displayName: 'Blog', - name: 'blog', - type: 'boolean', - default: false, - description: 'Whether to analyze the blog', - routing: { send: { type: 'body', property: 'specific_pages.blog' } }, - }, - { - displayName: 'Careers', - name: 'careers', - type: 'boolean', - default: false, - description: 'Whether to analyze the careers page', - routing: { send: { type: 'body', property: 'specific_pages.careers' } }, - }, - { - displayName: 'Contact Us', - name: 'contact_us', - type: 'boolean', - default: false, - description: 'Whether to analyze the contact us page', - routing: { send: { type: 'body', property: 'specific_pages.contact_us' } }, - }, - { - displayName: 'FAQ', - name: 'faq', - type: 'boolean', - default: false, - description: 'Whether to analyze the FAQ page', - routing: { send: { type: 'body', property: 'specific_pages.faq' } }, - }, - { - displayName: 'Home Page', - name: 'home_page', - type: 'boolean', - default: false, - description: 'Whether to analyze the home page', - routing: { send: { type: 'body', property: 'specific_pages.home_page' } }, - }, - { - displayName: 'Pricing', - name: 'pricing', - type: 'boolean', - default: false, - description: 'Whether to analyze the pricing page', - routing: { send: { type: 'body', property: 'specific_pages.pricing' } }, - }, - { - displayName: 'Privacy Policy', - name: 'privacy_policy', - type: 'boolean', - default: false, - description: 'Whether to analyze the privacy policy page', - routing: { send: { type: 'body', property: 'specific_pages.privacy_policy' } }, - }, - { - displayName: 'Terms and Conditions', - name: 'terms_and_conditions', - type: 'boolean', - default: false, - description: 'Whether to analyze the terms and conditions page', - routing: { send: { type: 'body', property: 'specific_pages.terms_and_conditions' } }, - }, - ], - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { ...show, operation: ['aiQuery', 'extractProducts', 'extractProduct'] }, - }, - options: [ - { - displayName: 'Max Products', - name: 'maxProducts', - type: 'number', - displayOptions: { show: { '/operation': ['extractProducts'] } }, - default: 5, - typeOptions: { minValue: 1 }, - routing: { request: { body: { maxProducts: '={{ $value }}' } } }, - }, - { - displayName: 'Max Age (Ms)', - name: 'maxAgeMs', - type: 'number', - displayOptions: { show: { '/operation': ['extractProduct', 'extractProducts'] } }, - default: 604800000, - typeOptions: { minValue: 0, maxValue: 2592000000 }, - description: - 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 7 days. Max is 30 days. Set to 0 to always scrape fresh.', - routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, - }, - { - displayName: 'Pages to Analyze', - name: 'specific_pages', - type: 'multiOptions', - displayOptions: { show: { '/operation': ['aiQuery'] } }, - default: [], - description: 'Specific pages to analyze on the website', - options: [ - { name: 'About Us', value: 'about_us' }, - { name: 'Blog', value: 'blog' }, - { name: 'Careers', value: 'careers' }, - { name: 'Contact Us', value: 'contact_us' }, - { name: 'FAQ', value: 'faq' }, - { name: 'Home Page', value: 'home_page' }, - { name: 'Pricing', value: 'pricing' }, - { name: 'Privacy Policy', value: 'privacy_policy' }, - { name: 'Terms & Conditions', value: 'terms_and_conditions' }, - ], - routing: { - request: { - body: { - specific_pages: - '={{ Object.fromEntries($value.map((k) => [k, true])) }}', - }, - }, - }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/resources/batch/index.ts b/nodes/ContextDev/resources/batch/index.ts new file mode 100644 index 0000000..0366757 --- /dev/null +++ b/nodes/ContextDev/resources/batch/index.ts @@ -0,0 +1,392 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const batchDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['batch'] } }, + options: [ + { name: 'Cancel a Batch', value: 'cancelBatch', action: 'Cancel a batch', description: 'Stop a batch from starting new pages. In-progress pages finish, and unused credits are refunded.', routing: { request: { method: 'POST', url: '=/batch/{{ $parameter["batchId"] }}/cancel' } } }, + { name: 'Delete a Batch', value: 'deleteBatch', action: 'Delete a batch', description: 'Permanently delete a finished batch and its stored results. Active batches must settle first.', routing: { request: { method: 'DELETE', url: '=/batch/{{ $parameter["batchId"] }}' } } }, + { name: 'Get a Batch', value: 'getBatch', action: 'Get a batch', description: 'Check progress, and get download links once the batch finishes', routing: { request: { method: 'GET', url: '=/batch/{{ $parameter["batchId"] }}' } } }, + { name: 'Get Batch Results', value: 'getBatchResults', action: 'Get batch results', description: 'Page through a finished batch\'s results as JSON instead of downloading the NDJSON files', routing: { request: { method: 'GET', url: '=/batch/{{ $parameter["batchId"] }}/results' } } }, + { name: 'List Batches', value: 'listBatches', action: 'List batches', description: 'List your batches from newest to oldest. Filter by status or continue with a cursor.', routing: { request: { method: 'GET', url: '/batch/list' } } }, + { name: 'Submit a Batch', value: 'submitBatch', action: 'Submit a batch', description: 'Scrape 25K URLs or crawl large websites asynchronously', routing: { request: { method: 'POST', url: '/batch/submit' } } }, + ], + default: 'submitBatch', + }, + { + displayName: 'Mode', + name: 'inputMode', + type: 'options', + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + required: true, + default: "scrape", + options: [{ name: 'Crawl', value: "crawl" }, { name: 'Scrape', value: "scrape" }], + description: 'Scrape the pages in `data.URLs`', + routing: { send: { property: 'input.mode', type: 'body' } }, + }, + { + displayName: 'Format', + name: 'inputDataFormat', + type: 'options', + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + required: true, + default: "markdown", + options: [{ name: 'HTML', value: "html" }, { name: 'Markdown', value: "markdown" }], + description: 'Return page content as Markdown', + routing: { send: { property: 'input.data.format', type: 'body' } }, + }, + { + displayName: 'URLs', + name: 'inputDataUrls', + type: 'string', + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + required: true, + default: "", + description: 'Pages to scrape. Maximum 25000.', + routing: { send: { property: 'input.data.urls', type: 'body' } }, + }, + { + displayName: 'Type', + name: 'inputDataSourceType', + type: 'options', + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + required: true, + default: "start_url", + options: [{ name: 'Sitemap', value: "sitemap" }, { name: 'Start URL', value: "start_url" }], + description: 'Start from one page', + routing: { send: { property: 'input.data.source.type', type: 'body' } }, + }, + { + displayName: 'URL', + name: 'inputDataSourceUrl', + type: 'string', + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + required: true, + default: "", + description: 'Page where crawling begins. A URL without a scheme is read as https://.', + routing: { send: { property: 'input.data.source.url', type: 'body' } }, + }, + { + displayName: 'Domain', + name: 'inputDataSourceDomain', + type: 'string', + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + required: true, + default: "", + description: 'Domain whose sitemap lists the pages to scrape. A full URL is reduced to its domain.', + routing: { send: { property: 'input.data.source.domain', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['batch'], operation: ['submitBatch'] } }, + options: [ + { + displayName: 'Country', + name: 'inputDataOptionsCountry', + type: 'options', + default: "ad", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'Ao', value: "ao" }, { name: 'Ar', value: "ar" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Bq', value: "bq" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cv', value: "cv" }, { name: 'Cw', value: "cw" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gg', value: "gg" }, { name: 'Gh', value: "gh" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'Im', value: "im" }, { name: 'In', value: "in" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Je', value: "je" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Kn', value: "kn" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Me', value: "me" }, { name: 'Mf', value: "mf" }, { name: 'Mg', value: "mg" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Si', value: "si" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'Ss', value: "ss" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sx', value: "sx" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Fetch the target page through a residential proxy in this country (ISO 3166-1 alpha-2)', + routing: { send: { property: 'input.data.options.country', type: 'body' } }, + }, + { + displayName: 'End', + name: 'inputDataOptionsPdfEnd', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last 1-based PDF page to parse. When omitted, parsing ends at the last page. Must be greater than or equal to start when both are provided.', + routing: { send: { property: 'input.data.options.pdf.end', type: 'body' } }, + }, + { + displayName: 'Exclude Selectors', + name: 'inputDataOptionsExcludeSelectors', + type: 'string', + default: "", + description: 'Remove elements matching these CSS selectors. Applied after `includeSelectors`, so an element matching both is removed.', + routing: { send: { property: 'input.data.options.excludeSelectors', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'inputDataSourceControlsFollowSubdomains', + type: 'boolean', + default: false, + description: 'Whether follow links to subdomains', + routing: { send: { property: 'input.data.source.controls.followSubdomains', type: 'body' } }, + }, + { + displayName: 'Include HTML', + name: 'inputDataOptionsIncludeHTML', + type: 'boolean', + default: false, + description: 'Whether also include each page\'s HTML in its result record, as an `HTML` field alongside the Markdown', + routing: { send: { property: 'input.data.options.includeHTML', type: 'body' } }, + }, + { + displayName: 'Include Images', + name: 'inputDataOptionsIncludeImages', + type: 'boolean', + default: false, + description: 'Whether include image references in the Markdown', + routing: { send: { property: 'input.data.options.includeImages', type: 'body' } }, + }, + { + displayName: 'Include Links', + name: 'inputDataOptionsIncludeLinks', + type: 'boolean', + default: true, + description: 'Whether include links in the Markdown', + routing: { send: { property: 'input.data.options.includeLinks', type: 'body' } }, + }, + { + displayName: 'Include Selectors', + name: 'inputDataOptionsIncludeSelectors', + type: 'string', + default: "", + description: 'Keep only the subtrees matching these CSS selectors. Filtered pages are always fetched fresh, ignoring `maxAgeMs`.', + routing: { send: { property: 'input.data.options.includeSelectors', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Max Age MS', + name: 'inputDataOptionsMaxAgeMs', + type: 'string', + default: '', + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 1 day (86400000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh.', + routing: { send: { property: 'input.data.options.maxAgeMs', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Max Depth', + name: 'inputDataSourceControlsMaxDepth', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":50}, + description: 'Maximum link depth. Source pages are depth 0. No limit when omitted.', + routing: { send: { property: 'input.data.source.controls.maxDepth', type: 'body' } }, + }, + { + displayName: 'Max URLs', + name: 'inputDataSourceControlsMaxUrls', + type: 'number', + default: 100, + typeOptions: {"minValue":1,"maxValue":25000}, + description: 'Maximum pages to fetch. Unused reserved credits are refunded. Maximum 25000.', + routing: { send: { property: 'input.data.source.controls.maxUrls', type: 'body' } }, + }, + { + displayName: 'OCR', + name: 'inputDataOptionsPdfOcr', + type: 'boolean', + default: false, + description: 'Whether true, OCR the selected PDF pages that have no usable text layer (scans), replacing each recovered page\'s text with the OCR result while pages with a real text layer keep it. Billed at 1 credit per page OCR actually recovered, on top of the base request cost. When false, no OCR runs.', + routing: { send: { property: 'input.data.options.pdf.ocr', type: 'body' } }, + }, + { + displayName: 'Regex', + name: 'inputDataSourceControlsRegex', + type: 'string', + default: "", + description: 'RE2 pattern for URLs to include. The `start_url` itself is always included.', + routing: { send: { property: 'input.data.source.controls.regex', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Settle Animations', + name: 'inputDataOptionsSettleAnimations', + type: 'boolean', + default: false, + description: 'Whether wait briefly for CSS and transition animations to settle before extraction, on pages that render in a browser', + routing: { send: { property: 'input.data.options.settleAnimations', type: 'body' } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'inputDataOptionsShortenBase64Images', + type: 'boolean', + default: true, + description: 'Whether shorten inline base64 image data', + routing: { send: { property: 'input.data.options.shortenBase64Images', type: 'body' } }, + }, + { + displayName: 'Should Parse', + name: 'inputDataOptionsPdfShouldParse', + type: 'boolean', + default: true, + description: 'Whether true, PDF URLs are fetched and parsed. When false, PDF URLs are skipped and a 400 PDF_SKIPPED is returned.', + routing: { send: { property: 'input.data.options.pdf.shouldParse', type: 'body' } }, + }, + { + displayName: 'Start', + name: 'inputDataOptionsPdfStart', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First 1-based PDF page to parse. When omitted, parsing starts at the first page.', + routing: { send: { property: 'input.data.options.pdf.start', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Tags stored on the batch. Filter the batch list by them later.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Use Main Content Only', + name: 'inputDataOptionsUseMainContentOnly', + type: 'boolean', + default: false, + description: 'Whether return the main content without navigation or footers', + routing: { send: { property: 'input.data.options.useMainContentOnly', type: 'body' } }, + }, + { + displayName: 'Wait for MS', + name: 'inputDataOptionsWaitForMs', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":15000}, + description: 'How long to wait after initial page load, in milliseconds. `0` waits 500 ms.', + routing: { send: { property: 'input.data.options.waitForMs', type: 'body' } }, + }, + { + displayName: 'Webhook URL', + name: 'webhookUrl', + type: 'string', + default: "", + description: 'URL notified when the batch finishes', + routing: { send: { property: 'webhookUrl', type: 'body', value: '={{ $value || undefined }}' } }, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['batch'], operation: ['listBatches'] } }, + options: [ + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: "", + description: 'Cursor from the previous page', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Q', + name: 'q', + type: 'string', + default: "", + description: 'Free-text search term, matched against the batch ID, crawl source (start URL or sitemap domain), and tags', + routing: { request: { qs: { "q": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Search Type', + name: 'searchType', + type: 'options', + default: "exact", + options: [{ name: 'Exact', value: "exact" }, { name: 'Prefix', value: "prefix" }], + description: '`prefix` for as-you-type prefix matching (default), `exact` for full-token matching', + routing: { request: { qs: { "search_type": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + default: "queued", + options: [{ name: 'Cancelled', value: "cancelled" }, { name: 'Cancelling', value: "cancelling" }, { name: 'Completed', value: "completed" }, { name: 'Failed', value: "failed" }, { name: 'Queued', value: "queued" }, { name: 'Running', value: "running" }], + description: 'Filter by status', + routing: { request: { qs: { "status": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Comma-separated list of tags to filter by (matches batches having any of them)', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Batch ID', + name: 'batchId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['batch'], operation: ['getBatch'] } }, + description: 'ID of the batch to retrieve or cancel', + }, + { + displayName: 'Batch ID', + name: 'batchId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['batch'], operation: ['deleteBatch'] } }, + description: 'ID of the batch to retrieve or cancel', + }, + { + displayName: 'Batch ID', + name: 'batchId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['batch'], operation: ['getBatchResults'] } }, + description: 'ID of the batch to retrieve or cancel', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['batch'], operation: ['getBatchResults'] } }, + options: [ + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: "", + description: 'Next_cursor from the previous page', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Batch ID', + name: 'batchId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['batch'], operation: ['cancelBatch'] } }, + description: 'ID of the batch to retrieve or cancel', + }, +]; diff --git a/nodes/ContextDev/resources/brand/index.ts b/nodes/ContextDev/resources/brand/index.ts deleted file mode 100644 index 49859c0..0000000 --- a/nodes/ContextDev/resources/brand/index.ts +++ /dev/null @@ -1,480 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const show = { resource: ['brand'] }; - -export const brandDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show }, - options: [ - { - name: 'Identify From Transaction', - value: 'identifyFromTransaction', - action: 'Identify a brand from transaction data', - description: - 'Match merchant name from bank statement to a brand. View docs.', - routing: { request: { method: 'GET', url: '/brand/transaction_identifier' } }, - }, - { - name: 'Retrieve by Company Name', - value: 'retrieveByName', - action: 'Retrieve brand data by company name', - description: - 'Search by company name and retrieve brand data. View docs.', - routing: { request: { method: 'GET', url: '/brand/retrieve-by-name' } }, - }, - { - name: 'Retrieve by Domain', - value: 'retrieve', - action: 'Retrieve brand data by domain', - description: - 'Get logos, colors, description and more. View docs.', - routing: { request: { method: 'GET', url: '/brand/retrieve' } }, - }, - { - name: 'Retrieve by Email', - value: 'retrieveByEmail', - action: 'Retrieve brand data by email address', - description: - 'Extracts domain from email (excludes free/disposable). View docs.', - routing: { request: { method: 'GET', url: '/brand/retrieve-by-email' } }, - }, - { - name: 'Retrieve by ISIN', - value: 'retrieveByIsin', - action: 'Retrieve brand data by ISIN', - description: - 'Look up by International Securities Identification Number. View docs.', - routing: { request: { method: 'GET', url: '/brand/retrieve-by-isin' } }, - }, - { - name: 'Retrieve by Stock Ticker', - value: 'retrieveByTicker', - action: 'Retrieve brand data by stock ticker', - description: - 'Look up by stock ticker symbol. View docs.', - routing: { request: { method: 'GET', url: '/brand/retrieve-by-ticker' } }, - }, - { - name: 'Retrieve Simplified', - value: 'retrieveSimplified', - action: 'Retrieve simplified brand data', - description: - 'Faster response with only domain, title, colors, logos, backdrops. View docs.', - routing: { request: { method: 'GET', url: '/brand/retrieve-simplified' } }, - }, - ], - default: 'retrieve', - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['retrieve', 'retrieveSimplified'] } }, - default: '', - placeholder: 'stripe.com', - routing: { request: { qs: { domain: '={{ $value }}' } } }, - }, - { - displayName: 'Email', - name: 'email', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['retrieveByEmail'] } }, - default: '', - placeholder: 'contact@stripe.com', - description: 'Free email providers (gmail.com, yahoo.com) and disposable addresses are not allowed', - routing: { request: { qs: { email: '={{ $value }}' } } }, - }, - { - displayName: 'Company Name', - name: 'name', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['retrieveByName'] } }, - default: '', - placeholder: 'Stripe', - routing: { request: { qs: { name: '={{ $value }}' } } }, - }, - { - displayName: 'Stock Ticker', - name: 'ticker', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['retrieveByTicker'] } }, - default: '', - placeholder: 'AAPL', - routing: { request: { qs: { ticker: '={{ $value }}' } } }, - }, - { - displayName: 'ISIN', - name: 'isin', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['retrieveByIsin'] } }, - default: '', - placeholder: 'US0378331005', - routing: { request: { qs: { isin: '={{ $value }}' } } }, - }, - { - displayName: 'Transaction Info', - name: 'transaction_info', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['identifyFromTransaction'] } }, - default: '', - placeholder: 'STARBUCKS STORE #12345', - description: 'Merchant name as it appears on a bank statement', - routing: { request: { qs: { transaction_info: '={{ $value }}' } } }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - ...show, - operation: [ - 'retrieve', - 'retrieveByEmail', - 'retrieveByName', - 'retrieveByTicker', - 'retrieveByIsin', - 'retrieveSimplified', - 'identifyFromTransaction', - ], - }, - }, - options: [ - { - displayName: 'City', - name: 'city', - type: 'string', - displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, - default: '', - placeholder: 'San Francisco', - routing: { request: { qs: { city: '={{ $value }}' } } }, - }, - { - displayName: 'Country Code', - name: 'country_gl', - type: 'string', - displayOptions: { show: { '/operation': ['identifyFromTransaction', 'retrieveByName'] } }, - default: '', - placeholder: 'us', - description: 'ISO 3166-1 alpha-2 country code', - routing: { request: { qs: { country_gl: '={{ $value }}' } } }, - }, - { - displayName: 'Force Language', - name: 'force_language', - type: 'options', - displayOptions: { - show: { - '/operation': [ - 'identifyFromTransaction', - 'retrieve', - 'retrieveByEmail', - 'retrieveByName', - 'retrieveByTicker', - 'retrieveByIsin', - ], - }, - }, - default: 'english', - options: [ - { name: 'Afrikaans', value: 'afrikaans' }, - { name: 'Albanian', value: 'albanian' }, - { name: 'Amharic', value: 'amharic' }, - { name: 'Arabic', value: 'arabic' }, - { name: 'Armenian', value: 'armenian' }, - { name: 'Assamese', value: 'assamese' }, - { name: 'Aymara', value: 'aymara' }, - { name: 'Azeri', value: 'azeri' }, - { name: 'Basque', value: 'basque' }, - { name: 'Belarusian', value: 'belarusian' }, - { name: 'Bengali', value: 'bengali' }, - { name: 'Bosnian', value: 'bosnian' }, - { name: 'Bulgarian', value: 'bulgarian' }, - { name: 'Burmese', value: 'burmese' }, - { name: 'Cantonese', value: 'cantonese' }, - { name: 'Catalan', value: 'catalan' }, - { name: 'Cebuano', value: 'cebuano' }, - { name: 'Chinese', value: 'chinese' }, - { name: 'Corsican', value: 'corsican' }, - { name: 'Croatian', value: 'croatian' }, - { name: 'Czech', value: 'czech' }, - { name: 'Danish', value: 'danish' }, - { name: 'Dutch', value: 'dutch' }, - { name: 'English', value: 'english' }, - { name: 'Esperanto', value: 'esperanto' }, - { name: 'Estonian', value: 'estonian' }, - { name: 'Farsi', value: 'farsi' }, - { name: 'Fijian', value: 'fijian' }, - { name: 'Finnish', value: 'finnish' }, - { name: 'French', value: 'french' }, - { name: 'Galician', value: 'galician' }, - { name: 'Georgian', value: 'georgian' }, - { name: 'German', value: 'german' }, - { name: 'Greek', value: 'greek' }, - { name: 'Guarani', value: 'guarani' }, - { name: 'Gujarati', value: 'gujarati' }, - { name: 'Haitian Creole', value: 'haitian-creole' }, - { name: 'Hausa', value: 'hausa' }, - { name: 'Hawaiian', value: 'hawaiian' }, - { name: 'Hebrew', value: 'hebrew' }, - { name: 'Hindi', value: 'hindi' }, - { name: 'Hmong', value: 'hmong' }, - { name: 'Hungarian', value: 'hungarian' }, - { name: 'Icelandic', value: 'icelandic' }, - { name: 'Igbo', value: 'igbo' }, - { name: 'Indonesian', value: 'indonesian' }, - { name: 'Irish', value: 'irish' }, - { name: 'Italian', value: 'italian' }, - { name: 'Japanese', value: 'japanese' }, - { name: 'Javanese', value: 'javanese' }, - { name: 'Kannada', value: 'kannada' }, - { name: 'Kazakh', value: 'kazakh' }, - { name: 'Khmer', value: 'khmer' }, - { name: 'Kinyarwanda', value: 'kinyarwanda' }, - { name: 'Korean', value: 'korean' }, - { name: 'Kurdish', value: 'kurdish' }, - { name: 'Kyrgyz', value: 'kyrgyz' }, - { name: 'Lao', value: 'lao' }, - { name: 'Latin', value: 'latin' }, - { name: 'Latvian', value: 'latvian' }, - { name: 'Lingala', value: 'lingala' }, - { name: 'Lithuanian', value: 'lithuanian' }, - { name: 'Luxembourgish', value: 'luxembourgish' }, - { name: 'Macedonian', value: 'macedonian' }, - { name: 'Malagasy', value: 'malagasy' }, - { name: 'Malay', value: 'malay' }, - { name: 'Malayalam', value: 'malayalam' }, - { name: 'Maltese', value: 'maltese' }, - { name: 'Maori', value: 'maori' }, - { name: 'Marathi', value: 'marathi' }, - { name: 'Mongolian', value: 'mongolian' }, - { name: 'Nepali', value: 'nepali' }, - { name: 'Norwegian', value: 'norwegian' }, - { name: 'Odia', value: 'odia' }, - { name: 'Oromo', value: 'oromo' }, - { name: 'Pashto', value: 'pashto' }, - { name: 'Pidgin', value: 'pidgin' }, - { name: 'Polish', value: 'polish' }, - { name: 'Portuguese', value: 'portuguese' }, - { name: 'Punjabi', value: 'punjabi' }, - { name: 'Quechua', value: 'quechua' }, - { name: 'Romanian', value: 'romanian' }, - { name: 'Russian', value: 'russian' }, - { name: 'Samoan', value: 'samoan' }, - { name: 'Scottish Gaelic', value: 'scottish-gaelic' }, - { name: 'Serbian', value: 'serbian' }, - { name: 'Sesotho', value: 'sesotho' }, - { name: 'Shona', value: 'shona' }, - { name: 'Sindhi', value: 'sindhi' }, - { name: 'Sinhala', value: 'sinhala' }, - { name: 'Slovak', value: 'slovak' }, - { name: 'Slovene', value: 'slovene' }, - { name: 'Somali', value: 'somali' }, - { name: 'Spanish', value: 'spanish' }, - { name: 'Sundanese', value: 'sundanese' }, - { name: 'Swahili', value: 'swahili' }, - { name: 'Swedish', value: 'swedish' }, - { name: 'Tagalog', value: 'tagalog' }, - { name: 'Tajik', value: 'tajik' }, - { name: 'Tamil', value: 'tamil' }, - { name: 'Tatar', value: 'tatar' }, - { name: 'Telugu', value: 'telugu' }, - { name: 'Thai', value: 'thai' }, - { name: 'Tibetan', value: 'tibetan' }, - { name: 'Tigrinya', value: 'tigrinya' }, - { name: 'Tongan', value: 'tongan' }, - { name: 'Tswana', value: 'tswana' }, - { name: 'Turkish', value: 'turkish' }, - { name: 'Turkmen', value: 'turkmen' }, - { name: 'Ukrainian', value: 'ukrainian' }, - { name: 'Urdu', value: 'urdu' }, - { name: 'Uyghur', value: 'uyghur' }, - { name: 'Uzbek', value: 'uzbek' }, - { name: 'Vietnamese', value: 'vietnamese' }, - { name: 'Welsh', value: 'welsh' }, - { name: 'Wolof', value: 'wolof' }, - { name: 'Xhosa', value: 'xhosa' }, - { name: 'Yiddish', value: 'yiddish' }, - { name: 'Yoruba', value: 'yoruba' }, - { name: 'Zulu', value: 'zulu' }, - ], - routing: { request: { qs: { force_language: '={{ $value }}' } } }, - }, - { - displayName: 'High Confidence Only', - name: 'high_confidence_only', - type: 'boolean', - displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, - default: false, - description: 'Whether to only return a brand match when confidence is high', - routing: { request: { qs: { high_confidence_only: '={{ $value }}' } } }, - }, - { - displayName: 'Max Age (Ms)', - name: 'maxAgeMs', - type: 'number', - displayOptions: { - show: { - '/operation': [ - 'retrieve', - 'retrieveByEmail', - 'retrieveByName', - 'retrieveByTicker', - 'retrieveByIsin', - 'retrieveSimplified', - ], - }, - }, - default: 7776000000, - description: 'Max age of cached data in ms before a hard refresh. Default 3 months.', - typeOptions: { minValue: 86400000, maxValue: 31536000000 }, - routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, - }, - { - displayName: 'Max Speed', - name: 'maxSpeed', - type: 'boolean', - displayOptions: { - show: { - '/operation': [ - 'identifyFromTransaction', - 'retrieve', - 'retrieveByEmail', - 'retrieveByName', - 'retrieveByTicker', - 'retrieveByIsin', - ], - }, - }, - default: false, - description: 'Whether to skip time-consuming operations for a faster response at the cost of less data', - routing: { request: { qs: { maxSpeed: '={{ $value }}' } } }, - }, - { - displayName: 'MCC', - name: 'mcc', - type: 'string', - displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, - default: '', - placeholder: '5814', - description: 'Merchant Category Code', - routing: { request: { qs: { mcc: '={{ $value }}' } } }, - }, - { - // SDK types `phone` as number; we deliberately expose it as a string so users can - // enter real phone formats (leading +, spaces) that aren't valid JS numbers. The - // value is passed through verbatim as the `phone` query param. - displayName: 'Phone', - name: 'phone', - type: 'string', - displayOptions: { show: { '/operation': ['identifyFromTransaction'] } }, - default: '', - placeholder: '+14155551234', - description: 'Company phone number to help identify the brand', - routing: { request: { qs: { phone: '={{ $value }}' } } }, - }, - { - displayName: 'Ticker Exchange', - name: 'ticker_exchange', - type: 'options', - displayOptions: { show: { '/operation': ['retrieveByTicker'] } }, - default: 'NASDAQ', - options: [ - { name: 'AMEX (NYSE American)', value: 'AMEX' }, - { name: 'AMS (Amsterdam)', value: 'AMS' }, - { name: 'AQS (AQSE, UK)', value: 'AQS' }, - { name: 'ASX (Australia)', value: 'ASX' }, - { name: 'ATH (Athens)', value: 'ATH' }, - { name: 'BER (Berlin)', value: 'BER' }, - { name: 'BME (Madrid)', value: 'BME' }, - { name: 'BRU (Brussels)', value: 'BRU' }, - { name: 'BSE (Bombay)', value: 'BSE' }, - { name: 'BUD (Budapest)', value: 'BUD' }, - { name: 'BUE (Buenos Aires)', value: 'BUE' }, - { name: 'BVC (Colombia)', value: 'BVC' }, - { name: 'CBOE (Chicago)', value: 'CBOE' }, - { name: 'CNQ (Canadian Securities Exchange)', value: 'CNQ' }, - { name: 'CPH (Copenhagen)', value: 'CPH' }, - { name: 'DFM (Dubai)', value: 'DFM' }, - { name: 'DOH (Doha)', value: 'DOH' }, - { name: 'DUB (Dublin)', value: 'DUB' }, - { name: 'DUS (Dusseldorf)', value: 'DUS' }, - { name: 'DXE (Cboe Europe Equities)', value: 'DXE' }, - { name: 'EGX (Egypt)', value: 'EGX' }, - { name: 'FSX (Frankfurt)', value: 'FSX' }, - { name: 'HAM (Hamburg)', value: 'HAM' }, - { name: 'HEL (Helsinki)', value: 'HEL' }, - { name: 'HKSE (Hong Kong)', value: 'HKSE' }, - { name: 'HOSE (Ho Chi Minh)', value: 'HOSE' }, - { name: 'ICE (Iceland)', value: 'ICE' }, - { name: 'IOB (International Order Book, LSE)', value: 'IOB' }, - { name: 'IST (Istanbul)', value: 'IST' }, - { name: 'JKT (Jakarta)', value: 'JKT' }, - { name: 'JNB (Johannesburg)', value: 'JNB' }, - { name: 'JPX (Tokyo)', value: 'JPX' }, - { name: 'KLS (Kuala Lumpur)', value: 'KLS' }, - { name: 'KOE (Korea OTC/KONEX)', value: 'KOE' }, - { name: 'KSC (Korea Stock Exchange - KOSPI)', value: 'KSC' }, - { name: 'KUW (Kuwait)', value: 'KUW' }, - { name: 'LIS (Lisbon)', value: 'LIS' }, - { name: 'LSE (London)', value: 'LSE' }, - { name: 'MCX (Moscow)', value: 'MCX' }, - { name: 'MEX (Mexico)', value: 'MEX' }, - { name: 'MIL (Milan)', value: 'MIL' }, - { name: 'MUN (Munich)', value: 'MUN' }, - { name: 'NASDAQ', value: 'NASDAQ' }, - { name: 'NEO (Canada)', value: 'NEO' }, - { name: 'NSE (India)', value: 'NSE' }, - { name: 'NYSE', value: 'NYSE' }, - { name: 'NZE (New Zealand)', value: 'NZE' }, - { name: 'OSL (Oslo)', value: 'OSL' }, - { name: 'OTC (Over-the-Counter, US)', value: 'OTC' }, - { name: 'PAR (Paris)', value: 'PAR' }, - { name: 'PNK (Pink Sheets, US)', value: 'PNK' }, - { name: 'PRA (Prague)', value: 'PRA' }, - { name: 'RIS (Riga)', value: 'RIS' }, - { name: 'SAO (Sao Paulo)', value: 'SAO' }, - { name: 'SAU (Saudi Arabia)', value: 'SAU' }, - { name: 'SES (Singapore)', value: 'SES' }, - { name: 'SET (Thailand)', value: 'SET' }, - { name: 'SGO (Santiago)', value: 'SGO' }, - { name: 'SHH (Shanghai)', value: 'SHH' }, - { name: 'SHZ (Shenzhen)', value: 'SHZ' }, - { name: 'SIX (Switzerland)', value: 'SIX' }, - { name: 'STO (Stockholm)', value: 'STO' }, - { name: 'STU (Stuttgart)', value: 'STU' }, - { name: 'TAI (Taiwan)', value: 'TAI' }, - { name: 'TAL (Tallinn)', value: 'TAL' }, - { name: 'TLV (Tel Aviv)', value: 'TLV' }, - { name: 'TSX (Toronto)', value: 'TSX' }, - { name: 'TSXV (TSX Venture)', value: 'TSXV' }, - { name: 'TWO (Taipei OTC)', value: 'TWO' }, - { name: 'VIE (Vienna)', value: 'VIE' }, - { name: 'WSE (Warsaw)', value: 'WSE' }, - { name: 'XETRA (Deutsche Börse)', value: 'XETRA' }, - ], - routing: { request: { qs: { ticker_exchange: '={{ $value }}' } } }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/resources/brandIntelligence/index.ts b/nodes/ContextDev/resources/brandIntelligence/index.ts new file mode 100644 index 0000000..07f73f5 --- /dev/null +++ b/nodes/ContextDev/resources/brandIntelligence/index.ts @@ -0,0 +1,745 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const brandIntelligenceDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['brandIntelligence'] } }, + options: [ + { name: 'Brand Search', value: 'brandSearch', action: 'Brand search', description: 'Search indexed brands by name or domain', routing: { request: { method: 'GET', url: '/brand/search' } } }, + { name: 'Identify Brand From Transaction Data', value: 'identifyBrandFromTransactionData', action: 'Identify brand from transaction data', description: 'Endpoint specially designed for platforms that want to identify transaction data by the transaction title', routing: { request: { method: 'GET', url: '/brand/transaction_identifier' } } }, + { name: 'Retrieve Brand Data', value: 'retrieveBrandData', action: 'Retrieve brand data', description: 'Retrieve logos, backdrops, colors, industry, description, and more. Provide exactly one lookup identifier in the request body: a domain, company name, email address, stock ticker, transaction descriptor, or direct URL. Note: `by_direct_url` fetches brand data only from the provided URL — not from the entire internet', routing: { request: { method: 'POST', url: '/brand/retrieve' } } }, + { name: 'Retrieve Brand Data by Company Name', value: 'retrieveBrandDataByCompanyName', action: 'Retrieve brand data by company name', description: 'Retrieve brand information using a company name', routing: { request: { method: 'GET', url: '/brand/retrieve-by-name' } } }, + { name: 'Retrieve Brand Data by Domain', value: 'retrieveBrandDataByDomain', action: 'Retrieve brand data by domain', description: 'Retrieve logos, backdrops, colors, industry, description, and more from any domain', routing: { request: { method: 'GET', url: '/brand/retrieve' } } }, + { name: 'Retrieve Brand Data by Email Address', value: 'retrieveBrandDataByEmailAddress', action: 'Retrieve brand data by email address', description: 'Retrieve brand information using an email address while detecting disposable and free email addresses. Disposable and free email addresses (like gmail.com, yahoo.com) will throw a 422 error.', routing: { request: { method: 'GET', url: '/brand/retrieve-by-email' } } }, + { name: 'Retrieve Brand Data by ISIN', value: 'retrieveBrandDataByISIN', action: 'Retrieve brand data by ISIN', description: 'Retrieve brand information using an ISIN (International Securities Identification Number)', routing: { request: { method: 'GET', url: '/brand/retrieve-by-isin' } } }, + { name: 'Retrieve Brand Data by Stock Ticker', value: 'retrieveBrandDataByStockTicker', action: 'Retrieve brand data by stock ticker', description: 'Retrieve brand information using a stock ticker symbol', routing: { request: { method: 'GET', url: '/brand/retrieve-by-ticker' } } }, + { name: 'Retrieve Simplified Brand Data by Domain', value: 'retrieveSimplifiedBrandDataByDomain', action: 'Retrieve simplified brand data by domain', description: 'Returns a simplified version of brand data containing only essential information: domain, title, colors, logos, and backdrops. Optimized for faster responses and reduced data transfer.', routing: { request: { method: 'GET', url: '/brand/retrieve-simplified' } } }, + ], + default: 'retrieveBrandDataByDomain', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByDomain'] } }, + options: [ + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: "", + description: 'Domain name to retrieve brand data for (e.g., \'example.com\', \'google.com\'). Cannot be used with name or ticker parameters.', + routing: { request: { qs: { "domain": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { request: { qs: { "force_language": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. Works with all three lookup methods.', + routing: { request: { qs: { "maxSpeed": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: "", + description: 'Company name to retrieve brand data for (e.g., \'Apple Inc\'). Cannot be used with domain or ticker parameters.', + routing: { request: { qs: { "name": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Ticker', + name: 'ticker', + type: 'string', + default: "", + description: 'Stock ticker symbol to retrieve brand data for (e.g., \'AAPL\'). Cannot be used with domain or name parameters.', + routing: { request: { qs: { "ticker": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Ticker Exchange', + name: 'tickerExchange', + type: 'options', + default: "AMEX", + options: [{ name: 'AMEX', value: "AMEX" }, { name: 'AMS', value: "AMS" }, { name: 'AQS', value: "AQS" }, { name: 'ASX', value: "ASX" }, { name: 'ATH', value: "ATH" }, { name: 'BER', value: "BER" }, { name: 'BME', value: "BME" }, { name: 'BRU', value: "BRU" }, { name: 'BSE', value: "BSE" }, { name: 'BUD', value: "BUD" }, { name: 'BUE', value: "BUE" }, { name: 'BVC', value: "BVC" }, { name: 'CBOE', value: "CBOE" }, { name: 'CNQ', value: "CNQ" }, { name: 'CPH', value: "CPH" }, { name: 'DFM', value: "DFM" }, { name: 'DOH', value: "DOH" }, { name: 'DUB', value: "DUB" }, { name: 'DUS', value: "DUS" }, { name: 'DXE', value: "DXE" }, { name: 'EGX', value: "EGX" }, { name: 'FSX', value: "FSX" }, { name: 'HAM', value: "HAM" }, { name: 'HEL', value: "HEL" }, { name: 'HKSE', value: "HKSE" }, { name: 'HOSE', value: "HOSE" }, { name: 'ICE', value: "ICE" }, { name: 'IOB', value: "IOB" }, { name: 'IST', value: "IST" }, { name: 'JKT', value: "JKT" }, { name: 'JNB', value: "JNB" }, { name: 'JPX', value: "JPX" }, { name: 'KLS', value: "KLS" }, { name: 'KOE', value: "KOE" }, { name: 'KSC', value: "KSC" }, { name: 'KUW', value: "KUW" }, { name: 'LIS', value: "LIS" }, { name: 'LSE', value: "LSE" }, { name: 'MCX', value: "MCX" }, { name: 'MEX', value: "MEX" }, { name: 'MIL', value: "MIL" }, { name: 'MUN', value: "MUN" }, { name: 'NASDAQ', value: "NASDAQ" }, { name: 'NEO', value: "NEO" }, { name: 'NSE', value: "NSE" }, { name: 'NYSE', value: "NYSE" }, { name: 'NZE', value: "NZE" }, { name: 'OSL', value: "OSL" }, { name: 'OTC', value: "OTC" }, { name: 'PAR', value: "PAR" }, { name: 'PNK', value: "PNK" }, { name: 'PRA', value: "PRA" }, { name: 'RIS', value: "RIS" }, { name: 'SAO', value: "SAO" }, { name: 'SAU', value: "SAU" }, { name: 'SES', value: "SES" }, { name: 'SET', value: "SET" }, { name: 'SGO', value: "SGO" }, { name: 'SHH', value: "SHH" }, { name: 'SHZ', value: "SHZ" }, { name: 'SIX', value: "SIX" }, { name: 'STO', value: "STO" }, { name: 'STU', value: "STU" }, { name: 'TAI', value: "TAI" }, { name: 'TAL', value: "TAL" }, { name: 'TLV', value: "TLV" }, { name: 'TSX', value: "TSX" }, { name: 'TSXV', value: "TSXV" }, { name: 'TWO', value: "TWO" }, { name: 'VIE', value: "VIE" }, { name: 'WSE', value: "WSE" }, { name: 'XETRA', value: "XETRA" }], + description: 'Stock exchange code', + routing: { request: { qs: { "ticker_exchange": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "by_domain", + options: [{ name: 'By Direct URL', value: "by_direct_url" }, { name: 'By Domain', value: "by_domain" }, { name: 'By Email', value: "by_email" }, { name: 'By Name', value: "by_name" }, { name: 'By Ticker', value: "by_ticker" }, { name: 'By Transaction', value: "by_transaction" }], + description: 'Discriminator for domain-based brand retrieval', + routing: { send: { property: 'type', type: 'body' } }, + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "", + description: 'Domain name to retrieve brand data for (e.g., \'stripe.com\')', + routing: { send: { property: 'domain', type: 'body' } }, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "", + description: 'Company name to retrieve brand data for (e.g., \'Apple Inc\')', + routing: { send: { property: 'name', type: 'body' } }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + placeholder: 'name@email.com', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "", + description: 'Email address to retrieve brand data for (e.g., \'jane@stripe.com\')', + routing: { send: { property: 'email', type: 'body' } }, + }, + { + displayName: 'Ticker', + name: 'ticker', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "", + description: 'Stock ticker symbol to retrieve brand data for (e.g., \'AAPL\')', + routing: { send: { property: 'ticker', type: 'body' } }, + }, + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "", + description: 'Full http(s) URL to fetch brand data from (e.g., \'https://stripe.com/enterprise\'). Only this URL is fetched — not the entire internet.', + routing: { send: { property: 'direct_url', type: 'body' } }, + }, + { + displayName: 'Transaction Info', + name: 'transactionInfo', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + required: true, + default: "", + description: 'Transaction information to identify the brand', + routing: { send: { property: 'transaction_info', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandData'] } }, + options: [ + { + displayName: 'City', + name: 'city', + type: 'string', + default: "", + description: 'Optional city name to prioritize when searching for the brand', + routing: { send: { property: 'city', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Country Gl', + name: 'countryGl', + type: 'string', + default: "", + description: 'Optional country code hint (GL parameter) to specify the country when looking up by company name', + routing: { send: { property: 'country_gl', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { send: { property: 'force_language', type: 'body' } }, + }, + { + displayName: 'High Confidence Only', + name: 'highConfidenceOnly', + type: 'boolean', + default: false, + description: 'Whether set to true, the API performs additional verification to ensure the identified brand matches the transaction with high confidence', + routing: { send: { property: 'high_confidence_only', type: 'body' } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'number', + default: 0, + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { send: { property: 'maxAgeMs', type: 'body' } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data.', + routing: { send: { property: 'maxSpeed', type: 'body' } }, + }, + { + displayName: 'Mcc', + name: 'mcc', + type: 'number', + default: 0, + routing: { send: { property: 'mcc', type: 'body' } }, + }, + { + displayName: 'Phone', + name: 'phone', + type: 'number', + default: 0, + routing: { send: { property: 'phone', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Ticker Exchange', + name: 'tickerExchange', + type: 'string', + default: "", + description: 'Optional stock exchange for the ticker. Defaults to NASDAQ if not specified.', + routing: { send: { property: 'ticker_exchange', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByCompanyName'] } }, + required: true, + default: "", + description: 'Company name to retrieve brand data for (e.g., \'Apple Inc\', \'Microsoft Corporation\'). Must be 3-30 characters.', + routing: { request: { qs: { "name": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByCompanyName'] } }, + options: [ + { + displayName: 'Country Gl', + name: 'countryGl', + type: 'options', + default: "af", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'An', value: "an" }, { name: 'Ao', value: "ao" }, { name: 'Aq', value: "aq" }, { name: 'Ar', value: "ar" }, { name: 'As', value: "as" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bt', value: "bt" }, { name: 'Bv', value: "bv" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cc', value: "cc" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Ck', value: "ck" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cu', value: "cu" }, { name: 'Cv', value: "cv" }, { name: 'Cx', value: "cx" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Eh', value: "eh" }, { name: 'Er', value: "er" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fk', value: "fk" }, { name: 'Fm', value: "fm" }, { name: 'Fo', value: "fo" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gh', value: "gh" }, { name: 'Gi', value: "gi" }, { name: 'Gl', value: "gl" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gs', value: "gs" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hm', value: "hm" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'In', value: "in" }, { name: 'Io', value: "io" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Ki', value: "ki" }, { name: 'Km', value: "km" }, { name: 'Kn', value: "kn" }, { name: 'Kp', value: "kp" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Li', value: "li" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Mg', value: "mg" }, { name: 'Mh', value: "mh" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mp', value: "mp" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'MS', value: "ms" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Nf', value: "nf" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nr', value: "nr" }, { name: 'Nu', value: "nu" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pm', value: "pm" }, { name: 'Pn', value: "pn" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Pw', value: "pw" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sb', value: "sb" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Sh', value: "sh" }, { name: 'Si', value: "si" }, { name: 'Sj', value: "sj" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tf', value: "tf" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tk', value: "tk" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'To', value: "to" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tv', value: "tv" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Um', value: "um" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Va', value: "va" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Vu', value: "vu" }, { name: 'Wf', value: "wf" }, { name: 'Ws', value: "ws" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Two-letter ISO 3166-1 alpha-2 country code (GL parameter) used to localize search', + routing: { request: { qs: { "country_gl": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { request: { qs: { "force_language": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data.', + routing: { request: { qs: { "maxSpeed": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['brandSearch'] } }, + required: true, + default: "", + description: 'Search term, matched against the fields selected by queryBy (e.g. \'nike\', \'nike.com\', \'nik\')', + routing: { request: { qs: { "query": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['brandSearch'] } }, + options: [ + { + displayName: 'Autocomplete', + name: 'autocomplete', + type: 'boolean', + default: true, + description: 'Whether the search term matches by prefix, so partial words match as they are typed (e.g. \'nik\' matches Nike). Set to false to match whole words only.', + routing: { request: { qs: { "autocomplete": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Query By', + name: 'queryBy', + type: 'string', + default: '', + description: 'Fields to match the search term against, as a comma-separated list or repeated parameter: \'name\', \'domain\', or both. Defaults to both.', + routing: { request: { qs: { "queryBy": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Typo Tolerance', + name: 'typoTolerance', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":2}, + description: 'Maximum number of typos tolerated when matching, from 0 to 2. Defaults to 0 (no typo tolerance).', + routing: { request: { qs: { "typoTolerance": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + placeholder: 'name@email.com', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByEmailAddress'] } }, + required: true, + default: "", + description: 'Email address to retrieve brand data for (e.g., \'contact@example.com\'). The domain will be extracted from the email. Free email providers (gmail.com, yahoo.com, etc.) and disposable email addresses are not allowed.', + routing: { request: { qs: { "email": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByEmailAddress'] } }, + options: [ + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { request: { qs: { "force_language": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data.', + routing: { request: { qs: { "maxSpeed": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Ticker', + name: 'ticker', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByStockTicker'] } }, + required: true, + default: "", + description: 'Stock ticker symbol to retrieve brand data for (e.g., \'AAPL\', \'GOOGL\', \'BRK.A\'). Must be 1-15 characters, letters/numbers/dots only.', + routing: { request: { qs: { "ticker": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByStockTicker'] } }, + options: [ + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { request: { qs: { "force_language": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data.', + routing: { request: { qs: { "maxSpeed": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Ticker Exchange', + name: 'tickerExchange', + type: 'options', + default: "AMEX", + options: [{ name: 'AMEX', value: "AMEX" }, { name: 'AMS', value: "AMS" }, { name: 'AQS', value: "AQS" }, { name: 'ASX', value: "ASX" }, { name: 'ATH', value: "ATH" }, { name: 'BER', value: "BER" }, { name: 'BME', value: "BME" }, { name: 'BRU', value: "BRU" }, { name: 'BSE', value: "BSE" }, { name: 'BUD', value: "BUD" }, { name: 'BUE', value: "BUE" }, { name: 'BVC', value: "BVC" }, { name: 'CBOE', value: "CBOE" }, { name: 'CNQ', value: "CNQ" }, { name: 'CPH', value: "CPH" }, { name: 'DFM', value: "DFM" }, { name: 'DOH', value: "DOH" }, { name: 'DUB', value: "DUB" }, { name: 'DUS', value: "DUS" }, { name: 'DXE', value: "DXE" }, { name: 'EGX', value: "EGX" }, { name: 'FSX', value: "FSX" }, { name: 'HAM', value: "HAM" }, { name: 'HEL', value: "HEL" }, { name: 'HKSE', value: "HKSE" }, { name: 'HOSE', value: "HOSE" }, { name: 'ICE', value: "ICE" }, { name: 'IOB', value: "IOB" }, { name: 'IST', value: "IST" }, { name: 'JKT', value: "JKT" }, { name: 'JNB', value: "JNB" }, { name: 'JPX', value: "JPX" }, { name: 'KLS', value: "KLS" }, { name: 'KOE', value: "KOE" }, { name: 'KSC', value: "KSC" }, { name: 'KUW', value: "KUW" }, { name: 'LIS', value: "LIS" }, { name: 'LSE', value: "LSE" }, { name: 'MCX', value: "MCX" }, { name: 'MEX', value: "MEX" }, { name: 'MIL', value: "MIL" }, { name: 'MUN', value: "MUN" }, { name: 'NASDAQ', value: "NASDAQ" }, { name: 'NEO', value: "NEO" }, { name: 'NSE', value: "NSE" }, { name: 'NYSE', value: "NYSE" }, { name: 'NZE', value: "NZE" }, { name: 'OSL', value: "OSL" }, { name: 'OTC', value: "OTC" }, { name: 'PAR', value: "PAR" }, { name: 'PNK', value: "PNK" }, { name: 'PRA', value: "PRA" }, { name: 'RIS', value: "RIS" }, { name: 'SAO', value: "SAO" }, { name: 'SAU', value: "SAU" }, { name: 'SES', value: "SES" }, { name: 'SET', value: "SET" }, { name: 'SGO', value: "SGO" }, { name: 'SHH', value: "SHH" }, { name: 'SHZ', value: "SHZ" }, { name: 'SIX', value: "SIX" }, { name: 'STO', value: "STO" }, { name: 'STU', value: "STU" }, { name: 'TAI', value: "TAI" }, { name: 'TAL', value: "TAL" }, { name: 'TLV', value: "TLV" }, { name: 'TSX', value: "TSX" }, { name: 'TSXV', value: "TSXV" }, { name: 'TWO', value: "TWO" }, { name: 'VIE', value: "VIE" }, { name: 'WSE', value: "WSE" }, { name: 'XETRA', value: "XETRA" }], + description: 'Stock exchange code', + routing: { request: { qs: { "ticker_exchange": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'ISIN', + name: 'isin', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByISIN'] } }, + required: true, + default: "", + description: 'ISIN (International Securities Identification Number) to retrieve brand data for (e.g., \'AU000000IMD5\', \'US0378331005\'). Must be exactly 12 characters: 2 letters followed by 9 alphanumeric characters and ending with a digit.', + routing: { request: { qs: { "isin": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveBrandDataByISIN'] } }, + options: [ + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { request: { qs: { "force_language": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data.', + routing: { request: { qs: { "maxSpeed": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Transaction Info', + name: 'transactionInfo', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['identifyBrandFromTransactionData'] } }, + required: true, + default: "", + description: 'Transaction information to identify the brand', + routing: { request: { qs: { "transaction_info": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['identifyBrandFromTransactionData'] } }, + options: [ + { + displayName: 'City', + name: 'city', + type: 'string', + default: "", + description: 'Optional city name to prioritize when searching for the brand', + routing: { request: { qs: { "city": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Country Gl', + name: 'countryGl', + type: 'options', + default: "af", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'An', value: "an" }, { name: 'Ao', value: "ao" }, { name: 'Aq', value: "aq" }, { name: 'Ar', value: "ar" }, { name: 'As', value: "as" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bt', value: "bt" }, { name: 'Bv', value: "bv" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cc', value: "cc" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Ck', value: "ck" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cu', value: "cu" }, { name: 'Cv', value: "cv" }, { name: 'Cx', value: "cx" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Eh', value: "eh" }, { name: 'Er', value: "er" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fk', value: "fk" }, { name: 'Fm', value: "fm" }, { name: 'Fo', value: "fo" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gh', value: "gh" }, { name: 'Gi', value: "gi" }, { name: 'Gl', value: "gl" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gs', value: "gs" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hm', value: "hm" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'In', value: "in" }, { name: 'Io', value: "io" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Ki', value: "ki" }, { name: 'Km', value: "km" }, { name: 'Kn', value: "kn" }, { name: 'Kp', value: "kp" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Li', value: "li" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Mg', value: "mg" }, { name: 'Mh', value: "mh" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mp', value: "mp" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'MS', value: "ms" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Nf', value: "nf" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nr', value: "nr" }, { name: 'Nu', value: "nu" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pm', value: "pm" }, { name: 'Pn', value: "pn" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Pw', value: "pw" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sb', value: "sb" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Sh', value: "sh" }, { name: 'Si', value: "si" }, { name: 'Sj', value: "sj" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tf', value: "tf" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tk', value: "tk" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'To', value: "to" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tv', value: "tv" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Um', value: "um" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Va', value: "va" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Vu', value: "vu" }, { name: 'Wf', value: "wf" }, { name: 'Ws', value: "ws" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Two-letter ISO 3166-1 alpha-2 country code (GL parameter) used to localize search', + routing: { request: { qs: { "country_gl": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Force Language', + name: 'forceLanguage', + type: 'options', + default: "afrikaans", + options: [{ name: 'Afrikaans', value: "afrikaans" }, { name: 'Albanian', value: "albanian" }, { name: 'Amharic', value: "amharic" }, { name: 'Arabic', value: "arabic" }, { name: 'Armenian', value: "armenian" }, { name: 'Assamese', value: "assamese" }, { name: 'Aymara', value: "aymara" }, { name: 'Azeri', value: "azeri" }, { name: 'Basque', value: "basque" }, { name: 'Belarusian', value: "belarusian" }, { name: 'Bengali', value: "bengali" }, { name: 'Bosnian', value: "bosnian" }, { name: 'Bulgarian', value: "bulgarian" }, { name: 'Burmese', value: "burmese" }, { name: 'Cantonese', value: "cantonese" }, { name: 'Catalan', value: "catalan" }, { name: 'Cebuano', value: "cebuano" }, { name: 'Chinese', value: "chinese" }, { name: 'Corsican', value: "corsican" }, { name: 'Croatian', value: "croatian" }, { name: 'Czech', value: "czech" }, { name: 'Danish', value: "danish" }, { name: 'Dutch', value: "dutch" }, { name: 'English', value: "english" }, { name: 'Esperanto', value: "esperanto" }, { name: 'Estonian', value: "estonian" }, { name: 'Farsi', value: "farsi" }, { name: 'Fijian', value: "fijian" }, { name: 'Finnish', value: "finnish" }, { name: 'French', value: "french" }, { name: 'Galician', value: "galician" }, { name: 'Georgian', value: "georgian" }, { name: 'German', value: "german" }, { name: 'Greek', value: "greek" }, { name: 'Guarani', value: "guarani" }, { name: 'Gujarati', value: "gujarati" }, { name: 'Haitian Creole', value: "haitian-creole" }, { name: 'Hausa', value: "hausa" }, { name: 'Hawaiian', value: "hawaiian" }, { name: 'Hebrew', value: "hebrew" }, { name: 'Hindi', value: "hindi" }, { name: 'Hmong', value: "hmong" }, { name: 'Hungarian', value: "hungarian" }, { name: 'Icelandic', value: "icelandic" }, { name: 'Igbo', value: "igbo" }, { name: 'Indonesian', value: "indonesian" }, { name: 'Irish', value: "irish" }, { name: 'Italian', value: "italian" }, { name: 'Japanese', value: "japanese" }, { name: 'Javanese', value: "javanese" }, { name: 'Kannada', value: "kannada" }, { name: 'Kazakh', value: "kazakh" }, { name: 'Khmer', value: "khmer" }, { name: 'Kinyarwanda', value: "kinyarwanda" }, { name: 'Korean', value: "korean" }, { name: 'Kurdish', value: "kurdish" }, { name: 'Kyrgyz', value: "kyrgyz" }, { name: 'Lao', value: "lao" }, { name: 'Latin', value: "latin" }, { name: 'Latvian', value: "latvian" }, { name: 'Lingala', value: "lingala" }, { name: 'Lithuanian', value: "lithuanian" }, { name: 'Luxembourgish', value: "luxembourgish" }, { name: 'Macedonian', value: "macedonian" }, { name: 'Malagasy', value: "malagasy" }, { name: 'Malay', value: "malay" }, { name: 'Malayalam', value: "malayalam" }, { name: 'Maltese', value: "maltese" }, { name: 'Maori', value: "maori" }, { name: 'Marathi', value: "marathi" }, { name: 'Mongolian', value: "mongolian" }, { name: 'Nepali', value: "nepali" }, { name: 'Norwegian', value: "norwegian" }, { name: 'Odia', value: "odia" }, { name: 'Oromo', value: "oromo" }, { name: 'Pashto', value: "pashto" }, { name: 'Pidgin', value: "pidgin" }, { name: 'Polish', value: "polish" }, { name: 'Portuguese', value: "portuguese" }, { name: 'Punjabi', value: "punjabi" }, { name: 'Quechua', value: "quechua" }, { name: 'Romanian', value: "romanian" }, { name: 'Russian', value: "russian" }, { name: 'Samoan', value: "samoan" }, { name: 'Scottish Gaelic', value: "scottish-gaelic" }, { name: 'Serbian', value: "serbian" }, { name: 'Sesotho', value: "sesotho" }, { name: 'Shona', value: "shona" }, { name: 'Sindhi', value: "sindhi" }, { name: 'Sinhala', value: "sinhala" }, { name: 'Slovak', value: "slovak" }, { name: 'Slovene', value: "slovene" }, { name: 'Somali', value: "somali" }, { name: 'Spanish', value: "spanish" }, { name: 'Sundanese', value: "sundanese" }, { name: 'Swahili', value: "swahili" }, { name: 'Swedish', value: "swedish" }, { name: 'Tagalog', value: "tagalog" }, { name: 'Tajik', value: "tajik" }, { name: 'Tamil', value: "tamil" }, { name: 'Tatar', value: "tatar" }, { name: 'Telugu', value: "telugu" }, { name: 'Thai', value: "thai" }, { name: 'Tibetan', value: "tibetan" }, { name: 'Tigrinya', value: "tigrinya" }, { name: 'Tongan', value: "tongan" }, { name: 'Tswana', value: "tswana" }, { name: 'Turkish', value: "turkish" }, { name: 'Turkmen', value: "turkmen" }, { name: 'Ukrainian', value: "ukrainian" }, { name: 'Urdu', value: "urdu" }, { name: 'Uyghur', value: "uyghur" }, { name: 'Uzbek', value: "uzbek" }, { name: 'Vietnamese', value: "vietnamese" }, { name: 'Welsh', value: "welsh" }, { name: 'Wolof', value: "wolof" }, { name: 'Xhosa', value: "xhosa" }, { name: 'Yiddish', value: "yiddish" }, { name: 'Yoruba', value: "yoruba" }, { name: 'Zulu', value: "zulu" }], + description: 'Language to force for the retrieved brand data', + routing: { request: { qs: { "force_language": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'High Confidence Only', + name: 'highConfidenceOnly', + type: 'boolean', + default: false, + description: 'Whether set to true, the API will perform an additional verification steps to ensure the identified brand matches the transaction with high confidence', + routing: { request: { qs: { "high_confidence_only": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Max Speed', + name: 'maxSpeed', + type: 'boolean', + default: false, + description: 'Whether optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data.', + routing: { request: { qs: { "maxSpeed": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Mcc', + name: 'mcc', + type: 'number', + default: 0, + routing: { request: { qs: { "mcc": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Phone', + name: 'phone', + type: 'number', + default: 0, + routing: { request: { qs: { "phone": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveSimplifiedBrandDataByDomain'] } }, + required: true, + default: "", + description: 'Domain name to retrieve simplified brand data for', + routing: { request: { qs: { "domain": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['brandIntelligence'], operation: ['retrieveSimplifiedBrandDataByDomain'] } }, + options: [ + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Theme', + name: 'theme', + type: 'options', + default: "light", + options: [{ name: 'Dark', value: "dark" }, { name: 'Light', value: "light" }], + description: 'Optional theme preference used when selecting brand assets', + routing: { request: { qs: { "theme": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/industry/index.ts b/nodes/ContextDev/resources/industry/index.ts deleted file mode 100644 index c1e3dfc..0000000 --- a/nodes/ContextDev/resources/industry/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const show = { resource: ['industry'] }; - -export const industryDescription: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show }, - options: [ - { - name: 'Classify NAICS', - value: 'retrieveNaics', - action: 'Classify a brand into NAICS industry codes', - description: - '2022 NAICS codes from domain or company name. View docs.', - routing: { request: { method: 'GET', url: '/web/naics' } }, - }, - { - name: 'Classify SIC', - value: 'retrieveSic', - action: 'Classify a brand into SIC industry codes', - description: - 'Standard Industrial Classification codes from domain or company name. View docs.', - routing: { request: { method: 'GET', url: '/web/sic' } }, - }, - ], - default: 'retrieveNaics', - }, - { - displayName: 'Input', - name: 'input', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['retrieveNaics', 'retrieveSic'] } }, - default: '', - placeholder: 'stripe.com or Stripe Inc', - description: 'Domain or company name to classify', - routing: { request: { qs: { input: '={{ $value }}' } } }, - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { ...show, operation: ['retrieveNaics', 'retrieveSic'] } }, - options: [ - { - displayName: 'Max Results', - name: 'maxResults', - type: 'number', - default: 5, - typeOptions: { minValue: 1, maxValue: 10 }, - routing: { request: { qs: { maxResults: '={{ $value }}' } } }, - }, - { - displayName: 'Min Results', - name: 'minResults', - type: 'number', - default: 1, - typeOptions: { minValue: 1 }, - routing: { request: { qs: { minResults: '={{ $value }}' } } }, - }, - { - displayName: 'SIC Dataset', - name: 'type', - type: 'options', - displayOptions: { show: { '/operation': ['retrieveSic'] } }, - default: 'original_sic', - options: [ - { name: 'Original SIC (1987)', value: 'original_sic' }, - { name: 'Latest SEC', value: 'latest_sec' }, - ], - routing: { request: { qs: { type: '={{ $value }}' } } }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/resources/monitors/index.ts b/nodes/ContextDev/resources/monitors/index.ts index c81a4c4..f9f4331 100644 --- a/nodes/ContextDev/resources/monitors/index.ts +++ b/nodes/ContextDev/resources/monitors/index.ts @@ -1,328 +1,447 @@ import type { INodeProperties } from 'n8n-workflow'; -const show = { resource: ['monitors'] }; - -// ops that carry a monitor_id path param -const idOps = ['get', 'update', 'delete', 'run', 'listChanges', 'listRuns']; -// list-family ops (pagination + filters) -const listOps = ['list', 'listAccountChanges', 'listAccountRuns', 'listChanges', 'listRuns']; - -// shared literals (create + update reuse these) -const UNIT_OPTIONS = [ - { name: 'Minutes', value: 'minutes' }, - { name: 'Hours', value: 'hours' }, - { name: 'Days', value: 'days' }, -]; -const CONFIDENCE_OPTS = { minValue: 0, maxValue: 1, numberPrecision: 2 }; -// comma-separated string -> trimmed non-empty array, sent on body key "tags" -const tagsRouting = { - request: { body: { tags: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' } }, -}; - -const operationField: INodeProperties = { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show }, - options: [ - { - name: 'Create Monitor', - value: 'create', - action: 'Create a monitor', - description: 'Create a website monitor that watches a page, sitemap, or extraction target for changes', - routing: { request: { method: 'POST', url: '/monitors' } }, - }, - { - name: 'Delete Monitor', - value: 'delete', - action: 'Delete a monitor', - description: 'Delete a monitor by ID', - routing: { request: { method: 'DELETE', url: '=/monitors/{{ $parameter["monitorId"] }}' } }, - }, - { - name: 'Get Change', - value: 'getChange', - action: 'Get a single change', - description: 'Retrieve a single detected change by its ID', - routing: { request: { method: 'GET', url: '=/monitors/changes/{{ $parameter["changeId"] }}' } }, - }, - { - name: 'Get Monitor', - value: 'get', - action: 'Get a monitor', - description: 'Retrieve a monitor by ID', - routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}' } }, - }, - { - name: 'List Account Changes', - value: 'listAccountChanges', - action: 'List changes across all monitors', - description: 'List detected changes across every monitor in the account', - routing: { request: { method: 'GET', url: '/monitors/changes' } }, - }, - { - name: 'List Account Runs', - value: 'listAccountRuns', - action: 'List runs across all monitors', - description: 'List monitor runs across every monitor in the account', - routing: { request: { method: 'GET', url: '/monitors/runs' } }, - }, - { - name: 'List Monitor Changes', - value: 'listChanges', - action: 'List changes for a monitor', - description: 'List detected changes for a single monitor', - routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/changes' } }, - }, - { - name: 'List Monitor Runs', - value: 'listRuns', - action: 'List runs for a monitor', - description: 'List runs for a single monitor', - routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/runs' } }, - }, - { - name: 'List Monitors', - value: 'list', - action: 'List monitors', - description: 'List monitors in the account', - routing: { request: { method: 'GET', url: '/monitors' } }, - }, - { - name: 'Run Monitor', - value: 'run', - action: 'Run a monitor now', - description: 'Trigger an immediate run of a monitor', - routing: { request: { method: 'POST', url: '=/monitors/{{ $parameter["monitorId"] }}/run' } }, - }, +export const monitorsDescription: INodeProperties[] = [ { - name: 'Update Monitor', - value: 'update', - action: 'Update a monitor', - description: 'Update a monitor by ID', - routing: { request: { method: 'PATCH', url: '=/monitors/{{ $parameter["monitorId"] }}' } }, + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['monitors'] } }, + options: [ + { name: 'Create a monitor', value: 'createMonitor', action: 'Create a monitor', description: 'Creates a monitor. The request body is a union of the supported target/change detection combinations. The monitor runs immediately after creation to create its initial baseline', routing: { request: { method: 'POST', url: '/monitors' } } }, + { name: 'Delete a monitor', value: 'deleteMonitor', action: 'Delete a monitor', routing: { request: { method: 'DELETE', url: '=/monitors/{{ $parameter["monitorId"] }}' } } }, + { name: 'Get a Change', value: 'getChange', action: 'Get a change', routing: { request: { method: 'GET', url: '=/monitors/changes/{{ $parameter["changeId"] }}' } } }, + { name: 'Get a monitor', value: 'getMonitor', action: 'Get a monitor', routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}' } } }, + { name: 'List Changes', value: 'listChanges', action: 'List changes', description: 'Returns an account-wide feed of detected changes across monitors', routing: { request: { method: 'GET', url: '/monitors/changes' } } }, + { name: 'List changes for a monitor', value: 'listMonitorChanges', action: 'List changes for a monitor', routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/changes' } } }, + { name: 'List Monitor Runs', value: 'listMonitorRuns', action: 'List monitor runs', routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/runs' } } }, + { name: 'List Monitors', value: 'listMonitors', action: 'List monitors', description: 'Lists monitors for the authenticated organization. Supports free-text search (`q` over `search_by` fields, `prefix` or `exact` via `search_type`) plus status/type/tag filters. Results are paginated via the opaque `cursor`', routing: { request: { method: 'GET', url: '/monitors' } } }, + { name: 'List Runs', value: 'listAccountRuns', action: 'List runs', description: 'Returns an account-wide feed of monitor runs across all monitors', routing: { request: { method: 'GET', url: '/monitors/runs' } } }, + { name: 'Monitor Credit Usage', value: 'listMonitorCreditUsage', action: 'Monitor credit usage', description: 'Returns credits charged per monitor over an optional [since, until] window, newest spenders first', routing: { request: { method: 'GET', url: '/monitors/credit-usage' } } }, + { name: 'Monitor Limits', value: 'getMonitorLimits', action: 'Monitor limits', description: 'Returns how many monitors the account has and the maximum it allows', routing: { request: { method: 'GET', url: '/monitors/limits' } } }, + { name: 'Retrieve a Monitor Run', value: 'getMonitorRun', action: 'Retrieve a monitor run', description: 'Fetches one run for a monitor, including lifecycle status, timing, credits charged, and any detected change', routing: { request: { method: 'GET', url: '=/monitors/{{ $parameter["monitorId"] }}/runs/{{ $parameter["runId"] }}' } } }, + { name: 'Rotate a Monitor Webhook Secret', value: 'rotateMonitorWebhookSecret', action: 'Rotate a monitor webhook secret', description: 'Generates a new signing secret for the monitor\'s webhook and returns the updated monitor (including the new `webhook.secret`). The previous secret stops signing deliveries immediately, so update your endpoint before rotating.', routing: { request: { method: 'POST', url: '=/monitors/{{ $parameter["monitorId"] }}/webhook/rotate-secret' } } }, + { name: 'Run a Monitor Now', value: 'runMonitorNow', action: 'Run a monitor now', description: 'Triggers an immediate run of the monitor outside its normal schedule. The run is queued and processed asynchronously.', routing: { request: { method: 'POST', url: '=/monitors/{{ $parameter["monitorId"] }}/run' } } }, + { name: 'Update a monitor', value: 'updateMonitor', action: 'Update a monitor', description: 'Updates a monitor. If `target` or `change_detection` changes, the monitor creates a new baseline. Unsupported target/change detection combinations are rejected', routing: { request: { method: 'PATCH', url: '=/monitors/{{ $parameter["monitorId"] }}' } } }, + ], + default: 'createMonitor', }, - ], - default: 'list', -}; - -// ─── path params ──────────────────────────────────────────────────────────────── -const pathParams: INodeProperties[] = [ - { - displayName: 'Monitor ID', - name: 'monitorId', - type: 'string', - required: true, - default: '', - placeholder: 'mon_123', - displayOptions: { show: { ...show, operation: idOps } }, - }, - { - displayName: 'Change ID', - name: 'changeId', - type: 'string', - required: true, - default: '', - placeholder: 'chg_123', - displayOptions: { show: { ...show, operation: ['getChange'] } }, - }, -]; - -// ─── create: core + discriminators ──────────────────────────────────────────────── -const createCore: INodeProperties[] = [ { displayName: 'Name', name: 'name', type: 'string', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, required: true, - default: '', - placeholder: 'Pricing page watcher', - displayOptions: { show: { ...show, operation: ['create'] } }, - routing: { send: { type: 'body', property: 'name' } }, + default: "", + routing: { send: { property: 'name', type: 'body' } }, }, { - displayName: 'Target Type', + displayName: 'Type', name: 'targetType', type: 'options', - default: 'page', - description: 'What to monitor. Sitemap targets pair with exact detection; extract targets pair with semantic detection.', - displayOptions: { show: { ...show, operation: ['create'] } }, - options: [ - { name: 'Page', value: 'page' }, - { name: 'Sitemap', value: 'sitemap' }, - { name: 'Extract', value: 'extract' }, - ], - routing: { send: { type: 'body', property: 'target.type' } }, + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "page", + options: [{ name: 'Extract', value: "extract" }, { name: 'Page', value: "page" }, { name: 'Sitemap', value: "sitemap" }], + routing: { send: { property: 'target.type', type: 'body' } }, }, { - displayName: 'Target URL', + displayName: 'URL', name: 'targetUrl', type: 'string', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, required: true, - default: '', - placeholder: 'https://stripe.com/pricing', - displayOptions: { show: { ...show, operation: ['create'] } }, - routing: { send: { type: 'body', property: 'target.url' } }, + default: "", + description: 'Sitemap URL to monitor', + routing: { send: { property: 'target.url', type: 'body' } }, }, { - displayName: 'Normalize Whitespace', - name: 'normalizeWhitespace', - type: 'boolean', - default: false, - description: 'Whether to normalize whitespace before comparing or analyzing text', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['page'] } }, - routing: { send: { type: 'body', property: 'target.normalize_whitespace' } }, + displayName: 'Instructions', + name: 'targetInstructions', + type: 'string', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "", + description: 'Plain-language goal describing which page changes matter. When provided without change_detection, semantic detection is inferred.', + routing: { send: { property: 'target.instructions', type: 'body' } }, }, { - displayName: 'Follow Subdomains', - name: 'followSubdomains', - type: 'boolean', - default: false, - description: 'Whether to follow links into subdomains while extracting', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, - routing: { send: { type: 'body', property: 'target.follow_subdomains' } }, + displayName: 'Target', + name: 'target', + type: 'json', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "{}", + description: 'Watch the monitor-relevant pages of a site for meaningful changes. A crawl guided by `schema`/`instructions` selects up to `max_pages` relevant pages to track; each run re-checks exactly those pages, and confirmed content changes are judged for relevance against the monitor\'s `instructions` (and `schema`, when provided). The tracked page set is refreshed by a periodic re-discovery crawl.', + routing: { send: { property: 'target', type: 'body', value: '={{ $value ? JSON.parse($value) : undefined }}' } }, }, { - displayName: 'Extract Instructions', - name: 'extractPrompt', - type: 'string', - default: '', - description: 'Optional natural-language instructions guiding what to extract', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, - routing: { send: { type: 'body', property: 'target.instructions' } }, + displayName: 'Type', + name: 'changeDetectionType', + type: 'options', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "exact", + options: [{ name: 'Exact', value: "exact" }, { name: 'Semantic', value: "semantic" }], + routing: { send: { property: 'change_detection.type', type: 'body' } }, }, { - displayName: 'Extract Schema (JSON)', - name: 'extractSchema', - type: 'json', - default: '', - description: 'Optional JSON Schema for the structured data to extract. Defaults to a summary + key-points schema.', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, - routing: { send: { type: 'body', property: 'target.schema', value: '={{ JSON.parse($value) }}' } }, + displayName: 'Type', + name: 'scheduleType', + type: 'options', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "interval", + options: [{ name: 'Interval', value: "interval" }], + routing: { send: { property: 'schedule.type', type: 'body' } }, }, { - displayName: 'Max Depth', - name: 'extractMaxDepth', + displayName: 'Frequency', + name: 'scheduleFrequency', type: 'number', - default: 0, - typeOptions: { minValue: 0 }, - description: 'Optional maximum link depth from the starting URL (0 = only the starting page)', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, - routing: { send: { type: 'body', property: 'target.max_depth' } }, + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: 1, + typeOptions: {"minValue":1,"maxValue":525600}, + description: 'Number of units between runs. The resulting interval (frequency × unit) must be at least 10 minutes and at most 1 year (e.g. minimum 10 when unit is minutes; maximum 365 when unit is days).', + routing: { send: { property: 'schedule.frequency', type: 'body' } }, }, { - displayName: 'Max Pages', - name: 'extractMaxPages', - type: 'number', - default: 10, - typeOptions: { minValue: 1 }, - description: 'Maximum number of pages to analyze during extraction', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['extract'] } }, - routing: { send: { type: 'body', property: 'target.max_pages' } }, + displayName: 'Unit', + name: 'scheduleUnit', + type: 'options', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "minutes", + options: [{ name: 'Days', value: "days" }, { name: 'Hours', value: "hours" }, { name: 'Minutes', value: "minutes" }], + routing: { send: { property: 'schedule.unit', type: 'body' } }, }, { - displayName: 'Exclude Patterns', - name: 'sitemapExclude', + displayName: 'URL', + name: 'webhookUrl', type: 'string', - default: '', - placeholder: '/blog/*,/legal/*', - description: 'Comma-separated URL path patterns to exclude', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['sitemap'] } }, - routing: { - send: { - type: 'body', - property: 'target.exclude', - value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + required: true, + default: "", + description: 'Webhook URL events are delivered to', + routing: { send: { property: 'webhook.url', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['monitors'], operation: ['createMonitor'] } }, + options: [ + { + displayName: 'Confidence Threshold', + name: 'changeDetectionConfidenceThreshold', + type: 'number', + default: 0.75, + typeOptions: {"minValue":0,"maxValue":1}, + routing: { send: { property: 'change_detection.confidence_threshold', type: 'body' } }, }, - }, + { + displayName: 'Events', + name: 'webhookEvents', + type: 'string', + default: "", + description: 'Events delivered to this endpoint. `change.detected` fires only when a run detects a change; `run.completed` fires on every completed run — including runs that detected no change — and embeds the change when one was detected. Defaults to `["change.detected"]` when omitted.', + routing: { send: { property: 'webhook.events', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Exclude', + name: 'targetExclude', + type: 'string', + default: "", + description: 'URL path patterns to exclude (max 50)', + routing: { send: { property: 'target.exclude', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'targetFollowSubdomains', + type: 'boolean', + default: false, + routing: { send: { property: 'target.follow_subdomains', type: 'body' } }, + }, + { + displayName: 'Include', + name: 'targetInclude', + type: 'string', + default: "", + description: 'URL path patterns to include (max 50)', + routing: { send: { property: 'target.include', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Max Depth', + name: 'targetMaxDepth', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":10}, + description: 'Optional maximum link depth from the starting URL (0 = only the starting page)', + routing: { send: { property: 'target.max_depth', type: 'body' } }, + }, + { + displayName: 'Max Pages', + name: 'targetMaxPages', + type: 'number', + default: 10, + typeOptions: {"minValue":1,"maxValue":50}, + description: 'Maximum number of pages to track', + routing: { send: { property: 'target.max_pages', type: 'body' } }, + }, + { + displayName: 'Max URLs', + name: 'targetMaxUrls', + type: 'number', + default: 5000, + typeOptions: {"minValue":1,"maxValue":10000}, + description: 'Maximum number of sitemap URLs to track (capped at 10,000)', + routing: { send: { property: 'target.max_urls', type: 'body' } }, + }, + { + displayName: 'Mode', + name: 'mode', + type: 'options', + default: "web", + options: [{ name: 'Web', value: "web" }], + description: 'Top-level monitor category. Always `web` today; the concrete behavior is described by `target` and `change_detection`.', + routing: { send: { property: 'mode', type: 'body' } }, + }, + { + displayName: 'Normalize Whitespace', + name: 'targetNormalizeWhitespace', + type: 'boolean', + default: true, + description: 'Whether normalize whitespace before comparing or analyzing text', + routing: { send: { property: 'target.normalize_whitespace', type: 'body' } }, + }, + { + displayName: 'Schema', + name: 'targetSchema', + type: 'json', + default: "{}", + description: 'JSON Schema describing the data you care about. It is used three ways: it guides which pages are selected for tracking, it gives the change judge extra context on which changes matter (alongside `instructions`), and it defines the shape of the baseline `data` snapshot on GET /monitors/{monitor_id} (refreshed at most about once a day). It is not a response format for changes: change events and webhook payloads always contain diffs, summaries, and evidence excerpts — never data in this schema\'s shape. If omitted, a default summary + key-points schema is used.', + routing: { send: { property: 'target.schema', type: 'body', value: '={{ $value ? JSON.parse($value) : undefined }}' } }, + }, + { + displayName: 'Secret', + name: 'webhookSecret', + type: 'string', + typeOptions: { password: true }, + default: "", + description: 'Signing secret used to verify webhook authenticity. Each delivery includes an `X-Context-Signature: t=<unix>,v1=<hmac>` header, where the HMAC is SHA-256 over `"{t}.{rawRequestBody}"` keyed by this secret. Recompute it with a constant-time compare and reject stale timestamps to prevent replay. Generated by the API; cannot be set by clients.', + routing: { send: { property: 'webhook.secret', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'User-defined tags for grouping and filtering monitors and their changes. Duplicates are removed.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Webhook', + name: 'webhook', + type: 'string', + default: "", + routing: { send: { property: 'webhook', type: 'body', value: '={{ $value || undefined }}' } }, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['monitors'], operation: ['listMonitors'] } }, + options: [ + { + displayName: 'Change Detection Type', + name: 'changeDetectionType', + type: 'options', + default: "exact", + options: [{ name: 'Exact', value: "exact" }, { name: 'Semantic', value: "semantic" }], + description: 'Filter by change detection type', + routing: { request: { qs: { "change_detection_type": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: "", + description: 'Opaque pagination cursor from a previous response', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Q', + name: 'q', + type: 'string', + default: "", + description: 'Free-text search term, matched against the fields named in `search_by`', + routing: { request: { qs: { "q": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Search By', + name: 'searchBy', + type: 'string', + default: "", + description: 'Comma-separated fields to search with `q`. Defaults to all of them. Note `instructions` only exists on extract monitors.', + routing: { request: { qs: { "search_by": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Search Type', + name: 'searchType', + type: 'options', + default: "exact", + options: [{ name: 'Exact', value: "exact" }, { name: 'Prefix', value: "prefix" }], + description: '`prefix` for as-you-type prefix matching (default), `exact` for full-token matching', + routing: { request: { qs: { "search_type": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + default: "active", + options: [{ name: 'Active', value: "active" }, { name: 'Failed', value: "failed" }, { name: 'Paused', value: "paused" }], + description: 'Filter monitors by lifecycle status', + routing: { request: { qs: { "status": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tag', + name: 'tag', + type: 'string', + default: "", + description: 'Filter to items that have this tag', + routing: { request: { qs: { "tag": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Comma-separated list of tags to filter by (matches monitors having any of them)', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Target Type', + name: 'targetType', + type: 'options', + default: "page", + options: [{ name: 'Extract', value: "extract" }, { name: 'Page', value: "page" }, { name: 'Sitemap', value: "sitemap" }], + description: 'Filter by target type', + routing: { request: { qs: { "target_type": '={{ $value || undefined }}' } } }, + }, + ], }, { - displayName: 'Include Patterns', - name: 'sitemapInclude', + displayName: 'Monitor ID', + name: 'monitorId', type: 'string', + required: true, default: '', - placeholder: '/pricing/*', - description: 'Comma-separated URL path patterns to include', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['sitemap'] } }, - routing: { - send: { - type: 'body', - property: 'target.include', - value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', - }, - }, + displayOptions: { show: { resource: ['monitors'], operation: ['getMonitor'] } }, }, { - displayName: 'Max URLs', - name: 'sitemapMaxUrls', - type: 'number', - default: 1000, - typeOptions: { minValue: 1 }, - description: 'Maximum number of URLs to monitor from the sitemap', - displayOptions: { show: { ...show, operation: ['create'], targetType: ['sitemap'] } }, - routing: { send: { type: 'body', property: 'target.max_urls' } }, + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, }, { - displayName: 'Change Detection', - name: 'changeDetectionType', + displayName: 'Type', + name: 'targetType', type: 'options', - default: 'exact', - displayOptions: { show: { ...show, operation: ['create'] } }, - options: [ - { name: 'Exact', value: 'exact' }, - { name: 'Semantic', value: 'semantic' }, - ], - routing: { send: { type: 'body', property: 'change_detection.type' } }, + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, + required: true, + default: "page", + options: [{ name: 'Extract', value: "extract" }, { name: 'Page', value: "page" }, { name: 'Sitemap', value: "sitemap" }], + routing: { send: { property: 'target.type', type: 'body' } }, }, { - displayName: 'Semantic Query', - name: 'semanticQuery', + displayName: 'URL', + name: 'targetUrl', type: 'string', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, required: true, - default: '', - placeholder: 'the monthly price of the Pro plan', - description: 'Natural-language query describing the meaning-level change to watch for', - displayOptions: { show: { ...show, operation: ['create'], changeDetectionType: ['semantic'] } }, - routing: { send: { type: 'body', property: 'change_detection.query' } }, + default: "", + description: 'Sitemap URL to monitor', + routing: { send: { property: 'target.url', type: 'body' } }, }, { - displayName: 'Confidence Threshold', - name: 'confidenceThreshold', - type: 'number', - default: 0.5, - typeOptions: CONFIDENCE_OPTS, - description: 'Minimum confidence (0–1) required to report a semantic change', - displayOptions: { show: { ...show, operation: ['create'], changeDetectionType: ['semantic'] } }, - routing: { send: { type: 'body', property: 'change_detection.confidence_threshold' } }, + displayName: 'Instructions', + name: 'targetInstructions', + type: 'string', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, + required: true, + default: "", + description: 'Plain-language goal describing which page changes matter. When provided without change_detection, semantic detection is inferred.', + routing: { send: { property: 'target.instructions', type: 'body' } }, }, { - displayName: 'Schedule Frequency', + displayName: 'Type', + name: 'changeDetectionType', + type: 'options', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, + required: true, + default: "exact", + options: [{ name: 'Exact', value: "exact" }, { name: 'Semantic', value: "semantic" }], + routing: { send: { property: 'change_detection.type', type: 'body' } }, + }, + { + displayName: 'Type', + name: 'scheduleType', + type: 'options', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, + required: true, + default: "interval", + options: [{ name: 'Interval', value: "interval" }], + routing: { send: { property: 'schedule.type', type: 'body' } }, + }, + { + displayName: 'Frequency', name: 'scheduleFrequency', type: 'number', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, required: true, - default: 6, - typeOptions: { minValue: 1 }, - description: 'Number of units between runs. The total interval (frequency × unit) must be between 10 minutes and 1 year.', - displayOptions: { show: { ...show, operation: ['create'] } }, - routing: { send: { type: 'body', property: 'schedule.frequency' } }, + default: 1, + typeOptions: {"minValue":1,"maxValue":525600}, + description: 'Number of units between runs. The resulting interval (frequency × unit) must be at least 10 minutes and at most 1 year (e.g. minimum 10 when unit is minutes; maximum 365 when unit is days).', + routing: { send: { property: 'schedule.frequency', type: 'body' } }, }, { - displayName: 'Schedule Unit', + displayName: 'Unit', name: 'scheduleUnit', type: 'options', - default: 'hours', - displayOptions: { show: { ...show, operation: ['create'] } }, - options: UNIT_OPTIONS, - routing: { send: { type: 'body', property: 'schedule.unit' } }, + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, + required: true, + default: "minutes", + options: [{ name: 'Days', value: "days" }, { name: 'Hours', value: "hours" }, { name: 'Minutes', value: "minutes" }], + routing: { send: { property: 'schedule.unit', type: 'body' } }, }, { - displayName: 'Schedule Type', - name: 'scheduleType', - type: 'hidden', - default: 'interval', - displayOptions: { show: { ...show, operation: ['create'] } }, - routing: { send: { type: 'body', property: 'schedule.type' } }, + displayName: 'URL', + name: 'webhookUrl', + type: 'string', + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, + required: true, + default: "", + description: 'Webhook URL events are delivered to', + routing: { send: { property: 'webhook.url', type: 'body' } }, }, { displayName: 'Additional Fields', @@ -330,408 +449,427 @@ const createCore: INodeProperties[] = [ type: 'collection', placeholder: 'Add Field', default: {}, - displayOptions: { show: { ...show, operation: ['create'] } }, + displayOptions: { show: { resource: ['monitors'], operation: ['updateMonitor'] } }, options: [ { - displayName: 'Tags', - name: 'tags', + displayName: 'Confidence Threshold', + name: 'changeDetectionConfidenceThreshold', + type: 'number', + default: 0.75, + typeOptions: {"minValue":0,"maxValue":1}, + routing: { send: { property: 'change_detection.confidence_threshold', type: 'body' } }, + }, + { + displayName: 'Events', + name: 'webhookEvents', + type: 'string', + default: "", + description: 'Events delivered to this endpoint. `change.detected` fires only when a run detects a change; `run.completed` fires on every completed run — including runs that detected no change — and embeds the change when one was detected. Defaults to `["change.detected"]` when omitted.', + routing: { send: { property: 'webhook.events', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Exclude', + name: 'targetExclude', + type: 'string', + default: "", + description: 'URL path patterns to exclude (max 50)', + routing: { send: { property: 'target.exclude', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'targetFollowSubdomains', + type: 'boolean', + default: false, + routing: { send: { property: 'target.follow_subdomains', type: 'body' } }, + }, + { + displayName: 'Include', + name: 'targetInclude', type: 'string', - default: '', - placeholder: 'pricing,marketing', - description: 'Comma-separated tags for grouping and filtering monitors and their changes', - routing: tagsRouting, + default: "", + description: 'URL path patterns to include (max 50)', + routing: { send: { property: 'target.include', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Max Depth', + name: 'targetMaxDepth', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":10}, + description: 'Optional maximum link depth from the starting URL (0 = only the starting page)', + routing: { send: { property: 'target.max_depth', type: 'body' } }, + }, + { + displayName: 'Max Pages', + name: 'targetMaxPages', + type: 'number', + default: 10, + typeOptions: {"minValue":1,"maxValue":50}, + description: 'Maximum number of pages to track', + routing: { send: { property: 'target.max_pages', type: 'body' } }, }, { - displayName: 'Webhook Secret', + displayName: 'Max URLs', + name: 'targetMaxUrls', + type: 'number', + default: 5000, + typeOptions: {"minValue":1,"maxValue":10000}, + description: 'Maximum number of sitemap URLs to track (capped at 10,000)', + routing: { send: { property: 'target.max_urls', type: 'body' } }, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: "", + routing: { send: { property: 'name', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Normalize Whitespace', + name: 'targetNormalizeWhitespace', + type: 'boolean', + default: true, + description: 'Whether normalize whitespace before comparing or analyzing text', + routing: { send: { property: 'target.normalize_whitespace', type: 'body' } }, + }, + { + displayName: 'Schema', + name: 'targetSchema', + type: 'json', + default: "{}", + description: 'JSON Schema describing the data you care about. It is used three ways: it guides which pages are selected for tracking, it gives the change judge extra context on which changes matter (alongside `instructions`), and it defines the shape of the baseline `data` snapshot on GET /monitors/{monitor_id} (refreshed at most about once a day). It is not a response format for changes: change events and webhook payloads always contain diffs, summaries, and evidence excerpts — never data in this schema\'s shape. If omitted, a default summary + key-points schema is used.', + routing: { send: { property: 'target.schema', type: 'body', value: '={{ $value ? JSON.parse($value) : undefined }}' } }, + }, + { + displayName: 'Secret', name: 'webhookSecret', type: 'string', typeOptions: { password: true }, - default: '', - description: - 'Signing secret used to verify webhook authenticity (each delivery is HMAC-signed with it)', - routing: { send: { type: 'body', property: 'webhook.secret' } }, + default: "", + description: 'Signing secret used to verify webhook authenticity. Each delivery includes an `X-Context-Signature: t=<unix>,v1=<hmac>` header, where the HMAC is SHA-256 over `"{t}.{rawRequestBody}"` keyed by this secret. Recompute it with a constant-time compare and reject stale timestamps to prevent replay. Generated by the API; cannot be set by clients.', + routing: { send: { property: 'webhook.secret', type: 'body', value: '={{ $value || undefined }}' } }, }, { - displayName: 'Webhook URL', - name: 'webhookUrl', + displayName: 'Status', + name: 'status', + type: 'options', + default: "active", + options: [{ name: 'Active', value: "active" }, { name: 'Paused', value: "paused" }], + routing: { send: { property: 'status', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', type: 'string', - default: '', - placeholder: 'https://example.com/hooks/context', - description: 'URL called when a change is detected', - routing: { send: { type: 'body', property: 'webhook.url' } }, + default: "", + description: 'User-defined tags for grouping and filtering monitors and their changes. Duplicates are removed.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Target', + name: 'target', + type: 'json', + default: "{}", + description: 'Watch the monitor-relevant pages of a site for meaningful changes. A crawl guided by `schema`/`instructions` selects up to `max_pages` relevant pages to track; each run re-checks exactly those pages, and confirmed content changes are judged for relevance against the monitor\'s `instructions` (and `schema`, when provided). The tracked page set is refreshed by a periodic re-discovery crawl.', + routing: { send: { property: 'target', type: 'body', value: '={{ $value ? JSON.parse($value) : undefined }}' } }, + }, + { + displayName: 'Webhook', + name: 'webhook', + type: 'string', + default: "", + routing: { send: { property: 'webhook', type: 'body', value: '={{ $value || undefined }}' } }, }, ], }, -]; - -// ─── update: all-optional mutable fields ────────────────────────────────────────── -const updateFields: INodeProperties[] = [ { - displayName: 'Update Fields', + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['deleteMonitor'] } }, + }, + { + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['listMonitorRuns'] } }, + }, + { + displayName: 'Additional Fields', name: 'additionalFields', type: 'collection', placeholder: 'Add Field', default: {}, - displayOptions: { show: { ...show, operation: ['update'] } }, + displayOptions: { show: { resource: ['monitors'], operation: ['listMonitorRuns'] } }, options: [ { - displayName: 'Change Detection Query', - name: 'updQuery', + displayName: 'Cursor', + name: 'cursor', type: 'string', - default: '', - description: 'Natural-language query for semantic detection', - routing: { send: { type: 'body', property: 'change_detection.query' } }, + default: "", + description: 'Opaque pagination cursor from a previous response', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, }, { - displayName: 'Change Detection Type', - name: 'updChangeDetectionType', - type: 'options', - default: 'exact', - description: 'Switch how changes are detected. Changing this creates a new baseline.', - options: [ - { name: 'Exact', value: 'exact' }, - { name: 'Semantic', value: 'semantic' }, - ], - routing: { send: { type: 'body', property: 'change_detection.type' } }, - }, - { - displayName: 'Confidence Threshold', - name: 'updConfidence', + displayName: 'Limit', + name: 'limit', type: 'number', - default: 0.5, - typeOptions: CONFIDENCE_OPTS, - routing: { send: { type: 'body', property: 'change_detection.confidence_threshold' } }, + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, }, { - displayName: 'Extract Instructions', - name: 'updExtractInstructions', - type: 'string', - default: '', - description: 'Optional natural-language instructions guiding what to extract', - routing: { send: { type: 'body', property: 'target.instructions' } }, + displayName: 'Status', + name: 'status', + type: 'options', + default: "queued", + options: [{ name: 'Completed', value: "completed" }, { name: 'Failed', value: "failed" }, { name: 'Queued', value: "queued" }, { name: 'Running', value: "running" }, { name: 'Skipped', value: "skipped" }], + description: 'Filter runs by lifecycle status', + routing: { request: { qs: { "status": '={{ $value || undefined }}' } } }, }, + ], + }, + { + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['getMonitorRun'] } }, + }, + { + displayName: 'Run ID', + name: 'runId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['getMonitorRun'] } }, + }, + { + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['listMonitorChanges'] } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['monitors'], operation: ['listMonitorChanges'] } }, + options: [ { - displayName: 'Extract Max Depth', - name: 'updExtractMaxDepth', - type: 'number', - default: 0, - typeOptions: { minValue: 0 }, - description: 'Optional maximum link depth from the starting URL (0 = only the starting page)', - routing: { send: { type: 'body', property: 'target.max_depth' } }, + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: "", + description: 'Opaque pagination cursor from a previous response', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, }, { - displayName: 'Extract Max Pages', - name: 'updExtractMaxPages', + displayName: 'Limit', + name: 'limit', type: 'number', - default: 10, - typeOptions: { minValue: 1 }, - description: 'Maximum number of pages to analyze during extraction', - routing: { send: { type: 'body', property: 'target.max_pages' } }, + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, }, { - displayName: 'Extract Schema (JSON)', - name: 'updExtractSchema', - type: 'json', - default: '', - description: 'JSON Schema describing the structured data to extract and watch for changes', - routing: { send: { type: 'body', property: 'target.schema', value: '={{ JSON.parse($value) }}' } }, + displayName: 'Since', + name: 'since', + type: 'string', + default: "", + description: 'Only include items at or after this ISO 8601 timestamp', + routing: { request: { qs: { "since": '={{ $value || undefined }}' } } }, }, { - displayName: 'Follow Subdomains', - name: 'updFollowSubdomains', - type: 'boolean', - default: false, - description: 'Whether to follow links into subdomains while extracting', - routing: { send: { type: 'body', property: 'target.follow_subdomains' } }, + displayName: 'Tag', + name: 'tag', + type: 'string', + default: "", + description: 'Filter to items that have this tag', + routing: { request: { qs: { "tag": '={{ $value || undefined }}' } } }, }, { - displayName: 'Name', - name: 'updName', + displayName: 'Until', + name: 'until', type: 'string', - default: '', - routing: { send: { type: 'body', property: 'name' } }, + default: "", + description: 'Only include items before this ISO 8601 timestamp', + routing: { request: { qs: { "until": '={{ $value || undefined }}' } } }, }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['monitors'], operation: ['listAccountRuns'] } }, + options: [ { - displayName: 'Normalize Whitespace', - name: 'updNormalizeWhitespace', - type: 'boolean', - default: false, - description: 'Whether to normalize whitespace before comparing or analyzing text', - routing: { send: { type: 'body', property: 'target.normalize_whitespace' } }, + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: "", + description: 'Opaque pagination cursor from a previous response', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, }, { - displayName: 'Remove Webhook', - name: 'updWebhookRemove', - type: 'boolean', - default: false, - description: 'Whether to remove the monitor\'s existing webhook (sends webhook: null)', - routing: { request: { body: { webhook: '={{ $value ? null : undefined }}' } } }, - }, - { - // The SDK requires schedule.type whenever schedule is sent, so frequency + unit - // are grouped and the constant type is injected — sent only when the group is set. - displayName: 'Schedule', - name: 'updSchedule', - type: 'fixedCollection', - default: {}, - description: 'Change the run interval (the required schedule type is sent automatically)', - options: [ - { - name: 'schedule', - displayName: 'Schedule', - values: [ - { - displayName: 'Frequency', - name: 'frequency', - type: 'number', - default: 6, - typeOptions: { minValue: 1 }, - }, - { - displayName: 'Unit', - name: 'unit', - type: 'options', - default: 'hours', - options: UNIT_OPTIONS, - }, - ], - }, - ], - routing: { - request: { - body: { - schedule: - '={{ { type: "interval", frequency: $value.schedule.frequency, unit: $value.schedule.unit } }}', - }, - }, - }, - }, - { - displayName: 'Sitemap Exclude Patterns', - name: 'updSitemapExclude', - type: 'string', - default: '', - placeholder: '/blog/*,/legal/*', - description: 'Comma-separated URL path patterns to exclude', - routing: { - send: { - type: 'body', - property: 'target.exclude', - value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', - }, - }, - }, - { - displayName: 'Sitemap Include Patterns', - name: 'updSitemapInclude', - type: 'string', - default: '', - placeholder: '/pricing/*', - description: 'Comma-separated URL path patterns to include', - routing: { - send: { - type: 'body', - property: 'target.include', - value: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', - }, - }, - }, - { - displayName: 'Sitemap Max URLs', - name: 'updSitemapMaxUrls', + displayName: 'Limit', + name: 'limit', type: 'number', - default: 1000, - typeOptions: { minValue: 1 }, - description: 'Maximum number of URLs to monitor from the sitemap', - routing: { send: { type: 'body', property: 'target.max_urls' } }, + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, }, { displayName: 'Status', - name: 'updStatus', - type: 'options', - default: 'active', - options: [ - { name: 'Active', value: 'active' }, - { name: 'Paused', value: 'paused' }, - ], - routing: { send: { type: 'body', property: 'status' } }, - }, - { - displayName: 'Tags', - name: 'updTags', - type: 'string', - default: '', - placeholder: 'pricing,marketing', - description: 'Comma-separated tags', - routing: tagsRouting, - }, - { - displayName: 'Target Type', - name: 'updTargetType', + name: 'status', type: 'options', - default: 'page', - description: 'Switch what the monitor watches. Changing this creates a new baseline.', - options: [ - { name: 'Page', value: 'page' }, - { name: 'Sitemap', value: 'sitemap' }, - { name: 'Extract', value: 'extract' }, - ], - routing: { send: { type: 'body', property: 'target.type' } }, - }, - { - displayName: 'Target URL', - name: 'updTargetUrl', - type: 'string', - default: '', - routing: { send: { type: 'body', property: 'target.url' } }, + default: "queued", + options: [{ name: 'Completed', value: "completed" }, { name: 'Failed', value: "failed" }, { name: 'Queued', value: "queued" }, { name: 'Running', value: "running" }, { name: 'Skipped', value: "skipped" }], + description: 'Filter runs by lifecycle status', + routing: { request: { qs: { "status": '={{ $value || undefined }}' } } }, }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['monitors'], operation: ['listMonitorCreditUsage'] } }, + options: [ { - displayName: 'Webhook Secret', - name: 'updWebhookSecret', + displayName: 'Since', + name: 'since', type: 'string', - typeOptions: { password: true }, - default: '', - description: 'Signing secret used to verify webhook authenticity', - routing: { send: { type: 'body', property: 'webhook.secret' } }, + default: "", + description: 'Only include items at or after this ISO 8601 timestamp', + routing: { request: { qs: { "since": '={{ $value || undefined }}' } } }, }, { - displayName: 'Webhook URL', - name: 'updWebhookUrl', + displayName: 'Until', + name: 'until', type: 'string', - default: '', - routing: { send: { type: 'body', property: 'webhook.url' } }, + default: "", + description: 'Only include items before this ISO 8601 timestamp', + routing: { request: { qs: { "until": '={{ $value || undefined }}' } } }, }, ], }, -]; - -// ─── list / changes / runs: pagination + filters ───────────────────────────────── -const runStatusOptions = [ - { name: 'Queued', value: 'queued' }, - { name: 'Running', value: 'running' }, - { name: 'Completed', value: 'completed' }, - { name: 'Failed', value: 'failed' }, -]; - -const listFilters: INodeProperties[] = [ { - displayName: 'Filters', + displayName: 'Additional Fields', name: 'additionalFields', type: 'collection', - placeholder: 'Add Filter', + placeholder: 'Add Field', default: {}, - displayOptions: { show: { ...show, operation: listOps } }, + displayOptions: { show: { resource: ['monitors'], operation: ['listChanges'] } }, options: [ { displayName: 'Change Detection Type', - name: 'change_detection_type', + name: 'changeDetectionType', type: 'options', - default: 'exact', - displayOptions: { show: { '/operation': ['list', 'listAccountChanges'] } }, - options: [ - { name: 'Exact', value: 'exact' }, - { name: 'Semantic', value: 'semantic' }, - ], - routing: { request: { qs: { change_detection_type: '={{ $value }}' } } }, + default: "exact", + options: [{ name: 'Exact', value: "exact" }, { name: 'Semantic', value: "semantic" }], + description: 'Filter by change detection type', + routing: { request: { qs: { "change_detection_type": '={{ $value || undefined }}' } } }, }, { displayName: 'Cursor', name: 'cursor', type: 'string', - default: '', - description: 'Pagination cursor from a previous response', - routing: { request: { qs: { cursor: '={{ $value }}' } } }, + default: "", + description: 'Opaque pagination cursor from a previous response', + routing: { request: { qs: { "cursor": '={{ $value || undefined }}' } } }, }, { displayName: 'Limit', name: 'limit', type: 'number', default: 50, - typeOptions: { minValue: 1 }, + typeOptions: {"minValue":1,"maxValue":100}, description: 'Max number of results to return', - routing: { request: { qs: { limit: '={{ $value }}' } } }, + routing: { request: { qs: { "limit": '={{ $value || undefined }}' } } }, }, { displayName: 'Monitor ID', - name: 'monitor_id', + name: 'monitorId', type: 'string', - default: '', - description: 'Filter account-wide changes to a single monitor', - displayOptions: { show: { '/operation': ['listAccountChanges'] } }, - routing: { request: { qs: { monitor_id: '={{ $value }}' } } }, - }, - { - displayName: 'Run Status', - name: 'runStatus', - type: 'options', - default: 'completed', - displayOptions: { show: { '/operation': ['listAccountRuns', 'listRuns'] } }, - options: runStatusOptions, - routing: { request: { qs: { status: '={{ $value }}' } } }, + default: "", + description: 'Filter changes to a single monitor', + routing: { request: { qs: { "monitor_id": '={{ $value || undefined }}' } } }, }, { displayName: 'Since', name: 'since', type: 'string', - default: '', - placeholder: '2026-01-01T00:00:00Z', - description: 'Only return changes at or after this ISO 8601 timestamp', - displayOptions: { show: { '/operation': ['listAccountChanges', 'listChanges'] } }, - routing: { request: { qs: { since: '={{ $value }}' } } }, - }, - { - displayName: 'Status', - name: 'status', - type: 'options', - default: 'active', - displayOptions: { show: { '/operation': ['list'] } }, - options: [ - { name: 'Active', value: 'active' }, - { name: 'Paused', value: 'paused' }, - { name: 'Failed', value: 'failed' }, - ], - routing: { request: { qs: { status: '={{ $value }}' } } }, + default: "", + description: 'Only include items at or after this ISO 8601 timestamp', + routing: { request: { qs: { "since": '={{ $value || undefined }}' } } }, }, { displayName: 'Tag', name: 'tag', type: 'string', - default: '', - description: 'Filter by a single tag', - displayOptions: { show: { '/operation': ['list', 'listAccountChanges', 'listChanges'] } }, - routing: { request: { qs: { tag: '={{ $value }}' } } }, + default: "", + description: 'Filter to items that have this tag', + routing: { request: { qs: { "tag": '={{ $value || undefined }}' } } }, }, { displayName: 'Target Type', - name: 'target_type', + name: 'targetType', type: 'options', - default: 'page', - displayOptions: { show: { '/operation': ['list', 'listAccountChanges'] } }, - options: [ - { name: 'Page', value: 'page' }, - { name: 'Sitemap', value: 'sitemap' }, - { name: 'Extract', value: 'extract' }, - ], - routing: { request: { qs: { target_type: '={{ $value }}' } } }, + default: "page", + options: [{ name: 'Extract', value: "extract" }, { name: 'Page', value: "page" }, { name: 'Sitemap', value: "sitemap" }], + description: 'Filter by target type', + routing: { request: { qs: { "target_type": '={{ $value || undefined }}' } } }, }, { displayName: 'Until', name: 'until', type: 'string', - default: '', - placeholder: '2026-02-01T00:00:00Z', - description: 'Only return changes at or before this ISO 8601 timestamp', - displayOptions: { show: { '/operation': ['listAccountChanges', 'listChanges'] } }, - routing: { request: { qs: { until: '={{ $value }}' } } }, + default: "", + description: 'Only include items before this ISO 8601 timestamp', + routing: { request: { qs: { "until": '={{ $value || undefined }}' } } }, }, ], }, -]; - -export const monitorsDescription: INodeProperties[] = [ - operationField, - ...pathParams, - ...createCore, - ...updateFields, - ...listFilters, + { + displayName: 'Change ID', + name: 'changeId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['getChange'] } }, + }, + { + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['runMonitorNow'] } }, + }, + { + displayName: 'Monitor ID', + name: 'monitorId', + type: 'string', + required: true, + default: '', + displayOptions: { show: { resource: ['monitors'], operation: ['rotateMonitorWebhookSecret'] } }, + }, ]; diff --git a/nodes/ContextDev/resources/news/index.ts b/nodes/ContextDev/resources/news/index.ts new file mode 100644 index 0000000..c090286 --- /dev/null +++ b/nodes/ContextDev/resources/news/index.ts @@ -0,0 +1,179 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const newsDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['news'] } }, + options: [ + { name: 'Search Company News', value: 'searchCompanyNews', action: 'Search company news', description: 'Searches live and historical company news for one company, identified in searchBy by name, domain, ticker (optionally disambiguated by exchange), or ISIN. Results can be filtered by publisher domain, publisher country, article language, article type, and published-at date, and include stable story IDs, source metadata, verified entity relevance, and cursor pagination.', routing: { request: { method: 'POST', url: '/news/search' } } }, + ], + default: 'searchCompanyNews', + }, + { + displayName: 'Type', + name: 'searchByType', + type: 'options', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "entity", + options: [{ name: 'Entity', value: "entity" }], + description: 'How to search. Only entity search is supported.', + routing: { send: { property: 'searchBy.type', type: 'body' } }, + }, + { + displayName: 'Type', + name: 'searchByEntityType', + type: 'options', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "name", + options: [{ name: 'Domain', value: "domain" }, { name: 'ISIN', value: "isin" }, { name: 'Name', value: "name" }, { name: 'Ticker', value: "ticker" }], + routing: { send: { property: 'searchBy.entity.type', type: 'body' } }, + }, + { + displayName: 'Name', + name: 'searchByEntityName', + type: 'string', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "", + description: 'Company name', + routing: { send: { property: 'searchBy.entity.name', type: 'body' } }, + }, + { + displayName: 'Domain', + name: 'searchByEntityDomain', + type: 'string', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "", + description: 'Company website domain, such as apple.com', + routing: { send: { property: 'searchBy.entity.domain', type: 'body' } }, + }, + { + displayName: 'Ticker', + name: 'searchByEntityTicker', + type: 'string', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "", + description: 'Public-company ticker', + routing: { send: { property: 'searchBy.entity.ticker', type: 'body' } }, + }, + { + displayName: 'ISIN', + name: 'searchByEntityIsin', + type: 'string', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "", + description: 'International Securities Identification Number', + routing: { send: { property: 'searchBy.entity.isin', type: 'body' } }, + }, + { + displayName: 'Type', + name: 'sortByType', + type: 'options', + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + required: true, + default: "relevance", + options: [{ name: 'Newest', value: "newest" }, { name: 'Relevance', value: "relevance" }], + description: 'Result ordering', + routing: { send: { property: 'sortBy.type', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['news'], operation: ['searchCompanyNews'] } }, + options: [ + { + displayName: 'Article Language', + name: 'filterByArticleLanguage', + type: 'string', + default: "", + description: 'Article languages to include. Up to 3.', + routing: { send: { property: 'filterBy.articleLanguage', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Article Type', + name: 'filterByArticleType', + type: 'string', + default: "", + description: 'Article types to include. Up to 3.', + routing: { send: { property: 'filterBy.articleType', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Cursor', + name: 'cursor', + type: 'string', + default: "", + description: 'Opaque next_cursor from the previous response, or null for the first page', + routing: { send: { property: 'cursor', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Exchange', + name: 'searchByEntityExchange', + type: 'options', + default: "AMEX", + options: [{ name: 'AMEX', value: "AMEX" }, { name: 'AMS', value: "AMS" }, { name: 'AQS', value: "AQS" }, { name: 'ASX', value: "ASX" }, { name: 'ATH', value: "ATH" }, { name: 'BER', value: "BER" }, { name: 'BME', value: "BME" }, { name: 'BRU', value: "BRU" }, { name: 'BSE', value: "BSE" }, { name: 'BUD', value: "BUD" }, { name: 'BUE', value: "BUE" }, { name: 'BVC', value: "BVC" }, { name: 'CBOE', value: "CBOE" }, { name: 'CNQ', value: "CNQ" }, { name: 'CPH', value: "CPH" }, { name: 'DFM', value: "DFM" }, { name: 'DOH', value: "DOH" }, { name: 'DUB', value: "DUB" }, { name: 'DUS', value: "DUS" }, { name: 'DXE', value: "DXE" }, { name: 'EGX', value: "EGX" }, { name: 'FSX', value: "FSX" }, { name: 'HAM', value: "HAM" }, { name: 'HEL', value: "HEL" }, { name: 'HKSE', value: "HKSE" }, { name: 'HOSE', value: "HOSE" }, { name: 'ICE', value: "ICE" }, { name: 'IOB', value: "IOB" }, { name: 'IST', value: "IST" }, { name: 'JKT', value: "JKT" }, { name: 'JNB', value: "JNB" }, { name: 'JPX', value: "JPX" }, { name: 'KLS', value: "KLS" }, { name: 'KOE', value: "KOE" }, { name: 'KSC', value: "KSC" }, { name: 'KUW', value: "KUW" }, { name: 'LIS', value: "LIS" }, { name: 'LSE', value: "LSE" }, { name: 'MCX', value: "MCX" }, { name: 'MEX', value: "MEX" }, { name: 'MIL', value: "MIL" }, { name: 'MUN', value: "MUN" }, { name: 'NASDAQ', value: "NASDAQ" }, { name: 'NEO', value: "NEO" }, { name: 'NSE', value: "NSE" }, { name: 'NYSE', value: "NYSE" }, { name: 'NZE', value: "NZE" }, { name: 'OSL', value: "OSL" }, { name: 'OTC', value: "OTC" }, { name: 'PAR', value: "PAR" }, { name: 'PNK', value: "PNK" }, { name: 'PRA', value: "PRA" }, { name: 'RIS', value: "RIS" }, { name: 'SAO', value: "SAO" }, { name: 'SAU', value: "SAU" }, { name: 'SES', value: "SES" }, { name: 'SET', value: "SET" }, { name: 'SGO', value: "SGO" }, { name: 'SHH', value: "SHH" }, { name: 'SHZ', value: "SHZ" }, { name: 'SIX', value: "SIX" }, { name: 'STO', value: "STO" }, { name: 'STU', value: "STU" }, { name: 'TAI', value: "TAI" }, { name: 'TAL', value: "TAL" }, { name: 'TLV', value: "TLV" }, { name: 'TSX', value: "TSX" }, { name: 'TSXV', value: "TSXV" }, { name: 'TWO', value: "TWO" }, { name: 'VIE', value: "VIE" }, { name: 'WSE', value: "WSE" }, { name: 'XETRA', value: "XETRA" }], + description: 'Stock exchange the ticker trades on, used to disambiguate tickers listed on multiple exchanges', + routing: { send: { property: 'searchBy.entity.exchange', type: 'body' } }, + }, + { + displayName: 'From', + name: 'filterByDateFrom', + type: 'number', + default: 0, + description: 'Inclusive start of the published-at window, in epoch milliseconds', + routing: { send: { property: 'filterBy.date.from', type: 'body' } }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + typeOptions: {"minValue":1,"maxValue":100}, + description: 'Max number of results to return', + routing: { send: { property: 'limit', type: 'body' } }, + }, + { + displayName: 'Source Country', + name: 'filterBySourceCountry', + type: 'string', + default: "", + description: 'Publisher countries to include, as lowercase ISO 3166-1 alpha-2 codes. Up to 3.', + routing: { send: { property: 'filterBy.sourceCountry', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Source Domain', + name: 'filterBySourceDomain', + type: 'string', + default: "", + description: 'Publisher domains to include. Up to 3.', + routing: { send: { property: 'filterBy.sourceDomain', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'To', + name: 'filterByDateTo', + type: 'number', + default: 0, + description: 'Inclusive end of the published-at window, in epoch milliseconds', + routing: { send: { property: 'filterBy.date.to', type: 'body' } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/parsing/index.ts b/nodes/ContextDev/resources/parsing/index.ts new file mode 100644 index 0000000..d8bf9b6 --- /dev/null +++ b/nodes/ContextDev/resources/parsing/index.ts @@ -0,0 +1,157 @@ +import type { IExecuteSingleFunctions, IHttpRequestOptions, INodeProperties } from 'n8n-workflow'; + +/** + * preSend hook for operations whose request body is raw bytes (application/octet-stream and + * friends). Reads the item's binary property, sends the buffer as the body, and replaces the + * node's default JSON Content-Type with the file's own mime type. + */ +export async function sendBinaryBody( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, +): Promise { + const propertyName = (this.getNodeParameter('binaryPropertyName') as string) || 'data'; + const binary = this.helpers.assertBinaryData(propertyName); + requestOptions.body = await this.helpers.getBinaryDataBuffer(propertyName); + requestOptions.headers = { + ...requestOptions.headers, + 'Content-Type': binary.mimeType || 'application/octet-stream', + }; + // the buffer is already the payload — stop the client from JSON-serialising it + requestOptions.json = false; + return requestOptions; +} + +export const parsingDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['parsing'] } }, + options: [ + { name: 'Parse Bytes', value: 'parseBytes', action: 'Parse bytes', description: 'Converts raw text, source code, web/data, PDF, Microsoft Office, and image bytes into LLM-usable Markdown', routing: { request: { method: 'POST', url: '/parse' } } }, + ], + default: 'parseBytes', + }, + { + displayName: 'Input Binary Field', + name: 'binaryPropertyName', + type: 'string', + required: true, + default: 'data', + displayOptions: { show: { resource: ['parsing'], operation: ['parseBytes'] } }, + description: 'Name of the binary property containing the file to send as the request body (accepts application/octet-stream, application/pdf, */*)', + routing: { send: { preSend: [sendBinaryBody] } }, + }, + { + displayName: 'PDF', + name: 'pdf', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['parsing'], operation: ['parseBytes'] } }, + options: [ + { + displayName: 'Start', + name: 'start', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First 1-based PDF page to parse. When omitted, parsing starts at the first page.', + }, + { + displayName: 'End', + name: 'end', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last 1-based PDF page to parse. When omitted, parsing ends at the last page. Must be greater than or equal to start when both are provided.', + }, + ], + routing: { request: { qs: { "pdf": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['parsing'], operation: ['parseBytes'] } }, + options: [ + { + displayName: 'Client', + name: 'client', + type: 'string', + default: "", + description: 'Optional client identifier used for usage attribution', + routing: { request: { qs: { "client": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Extension', + name: 'extension', + type: 'options', + default: "txt", + options: [{ name: 'Atom', value: "atom" }, { name: 'Bash', value: "bash" }, { name: 'Bmp', value: "bmp" }, { name: 'Cjs', value: "cjs" }, { name: 'CSS', value: "css" }, { name: 'Csv', value: "csv" }, { name: 'Doc', value: "doc" }, { name: 'Docx', value: "docx" }, { name: 'Fish', value: "fish" }, { name: 'Gif', value: "gif" }, { name: 'Htm', value: "htm" }, { name: 'HTML', value: "html" }, { name: 'Java', value: "java" }, { name: 'Jpe', value: "jpe" }, { name: 'Jpeg', value: "jpeg" }, { name: 'Jpg', value: "jpg" }, { name: 'Js', value: "js" }, { name: 'JSON', value: "json" }, { name: 'Jsonl', value: "jsonl" }, { name: 'Jsx', value: "jsx" }, { name: 'Less', value: "less" }, { name: 'Markdown', value: "markdown" }, { name: 'Md', value: "md" }, { name: 'Mjs', value: "mjs" }, { name: 'Ndjson', value: "ndjson" }, { name: 'Pbm', value: "pbm" }, { name: 'PDF', value: "pdf" }, { name: 'Pgm', value: "pgm" }, { name: 'Php', value: "php" }, { name: 'Png', value: "png" }, { name: 'Pnm', value: "pnm" }, { name: 'Pot', value: "pot" }, { name: 'Potm', value: "potm" }, { name: 'Potx', value: "potx" }, { name: 'Ppm', value: "ppm" }, { name: 'Pps', value: "pps" }, { name: 'Ppsm', value: "ppsm" }, { name: 'Ppsx', value: "ppsx" }, { name: 'Ppt', value: "ppt" }, { name: 'Pptm', value: "pptm" }, { name: 'Pptx', value: "pptx" }, { name: 'Py', value: "py" }, { name: 'Rb', value: "rb" }, { name: 'Rss', value: "rss" }, { name: 'Rtf', value: "rtf" }, { name: 'Sass', value: "sass" }, { name: 'Scss', value: "scss" }, { name: 'Sh', value: "sh" }, { name: 'Srt', value: "srt" }, { name: 'Styl', value: "styl" }, { name: 'Svg', value: "svg" }, { name: 'Text', value: "text" }, { name: 'Tif', value: "tif" }, { name: 'Tiff', value: "tiff" }, { name: 'Ts', value: "ts" }, { name: 'Tsv', value: "tsv" }, { name: 'Tsx', value: "tsx" }, { name: 'Txt', value: "txt" }, { name: 'Webp', value: "webp" }, { name: 'Xhtml', value: "xhtml" }, { name: 'Xls', value: "xls" }, { name: 'Xlsb', value: "xlsb" }, { name: 'Xlsm', value: "xlsm" }, { name: 'Xlsx', value: "xlsx" }, { name: 'Xltm', value: "xltm" }, { name: 'Xltx', value: "xltx" }, { name: 'Xml', value: "xml" }, { name: 'Yaml', value: "yaml" }, { name: 'Yml', value: "yml" }, { name: 'Zsh', value: "zsh" }], + description: 'Optional file extension hint, such as PDF, docx, xlsx, pptx, HTML, JSON, csv, md, py, rtf, jpg, png, or txt', + routing: { request: { qs: { "extension": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Include Images', + name: 'includeImages', + type: 'boolean', + default: false, + description: 'Whether include image references in Markdown output', + routing: { request: { qs: { "includeImages": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Include Links', + name: 'includeLinks', + type: 'boolean', + default: true, + description: 'Whether preserve hyperlinks in Markdown output', + routing: { request: { qs: { "includeLinks": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'OCR', + name: 'ocr', + type: 'boolean', + default: false, + description: 'Whether true for PDF inputs, OCR the selected pages that have no usable text layer (scans), replacing each recovered page\'s text with the OCR result while pages with a real text layer keep it. PDF.start/PDF.end limit the inclusive page range. Billed at 1 credit per page OCR actually recovered, on top of the base request cost. When false, no OCR runs.', + routing: { request: { qs: { "ocr": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'shortenBase64Images', + type: 'boolean', + default: true, + description: 'Whether shorten base64-encoded image data in the Markdown output', + routing: { request: { qs: { "shortenBase64Images": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Use Main Content Only', + name: 'useMainContentOnly', + type: 'boolean', + default: false, + description: 'Whether extract only the main content from HTML-like inputs', + routing: { request: { qs: { "useMainContentOnly": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'ZDR', + name: 'zdr', + type: 'options', + default: "disabled", + options: [{ name: 'Disabled', value: "disabled" }, { name: 'Enabled', value: "enabled" }], + description: 'Set to enabled to bypass shared caches and omit request and response content from retained usage logs. Requires zero data retention to be enabled for your organization (contact support@context.dev), otherwise the request fails with ZDR_NOT_ENABLED. Successful ZDR responses include X-Context-ZDR: true.', + routing: { request: { qs: { "zdr": '={{ $value || undefined }}' } } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/people/index.ts b/nodes/ContextDev/resources/people/index.ts new file mode 100644 index 0000000..f15b252 --- /dev/null +++ b/nodes/ContextDev/resources/people/index.ts @@ -0,0 +1,149 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const peopleDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['people'] } }, + options: [ + { name: 'Enrich Person', value: 'enrichPerson', action: 'Enrich person', description: 'Finds and normalizes the best available person candidate from additive identity clues, then assigns an identity match score from 0 to 100. Available on all paid plans. Successful requests cost 20 credits. Disposable and free email addresses (like gmail.com, yahoo.com) will throw a 422 error', routing: { request: { method: 'POST', url: '/people/enrich' } } }, + { name: 'Retrieve Person', value: 'retrievePerson', action: 'Retrieve person', description: 'Retrieve and normalize a person profile from identifiers', routing: { request: { method: 'POST', url: '/people/retrieve' } } }, + ], + default: 'enrichPerson', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['people'], operation: ['enrichPerson'] } }, + options: [ + { + displayName: 'City', + name: 'locationCity', + type: 'string', + default: "", + routing: { send: { property: 'location.city', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Country', + name: 'locationCountry', + type: 'string', + default: "", + routing: { send: { property: 'location.country', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Domain', + name: 'companyDomain', + type: 'string', + default: "", + routing: { send: { property: 'company.domain', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Education', + name: 'education', + type: 'string', + default: "", + routing: { send: { property: 'education', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + placeholder: 'name@email.com', + default: "", + routing: { send: { property: 'email', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'First', + name: 'nameFirst', + type: 'string', + default: "", + routing: { send: { property: 'name.first', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Last', + name: 'nameLast', + type: 'string', + default: "", + routing: { send: { property: 'name.last', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Name', + name: 'companyName', + type: 'string', + default: "", + routing: { send: { property: 'company.name', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Region', + name: 'locationRegion', + type: 'string', + default: "", + routing: { send: { property: 'location.region', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Social URLs', + name: 'socialUrls', + type: 'string', + default: "", + routing: { send: { property: 'social_urls', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['people'], operation: ['retrievePerson'] } }, + options: [ + { + displayName: 'Linkedin URL', + name: 'identifiersLinkedinUrl', + type: 'string', + default: "", + description: 'LinkedIn profile URL, e.g. https://www.linkedin.com/in/yahia-bakour/', + routing: { send: { property: 'identifiers.linkedinUrl', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/utility/index.ts b/nodes/ContextDev/resources/utility/index.ts index 994f2d1..8962082 100644 --- a/nodes/ContextDev/resources/utility/index.ts +++ b/nodes/ContextDev/resources/utility/index.ts @@ -1,54 +1,124 @@ import type { INodeProperties } from 'n8n-workflow'; -const show = { resource: ['utility'] }; - export const utilityDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['utility'] } }, + options: [ + { name: 'Prefetch Brand Data by Email', value: 'prefetchBrandDataByEmail', action: 'Prefetch brand data by email', description: 'Signal that you may fetch brand data for a particular domain soon to improve latency. This endpoint accepts an email address, extracts the domain from it, validates that it\'s not a disposable or free email provider, and queues the domain for prefetching.', routing: { request: { method: 'POST', url: '/brand/prefetch-by-email' } } }, + { name: 'Prefetch Brand Data for a Domain', value: 'prefetchBrandDataForADomain', action: 'Prefetch brand data for a domain', description: 'Signal that you may fetch brand data for a particular domain soon to improve latency', routing: { request: { method: 'POST', url: '/brand/prefetch' } } }, + { name: 'Prefetch Data', value: 'prefetchData', action: 'Prefetch data', description: 'Signal that you may fetch data soon to improve latency. The type field selects what to prefetch (\'brand\' queues a brand data fetch, \'styleguide\' queues a styleguide extraction) and identifier carries exactly one lookup key: a domain, or an email whose domain is extracted and validated (free email providers and disposable email addresses are not allowed).', routing: { request: { method: 'POST', url: '/utility/prefetch' } } }, + ], + default: 'prefetchBrandDataForADomain', + }, { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show }, + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['utility'], operation: ['prefetchBrandDataForADomain'] } }, + required: true, + default: "", + description: 'Domain name to prefetch brand data for', + routing: { send: { property: 'domain', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['utility'], operation: ['prefetchBrandDataForADomain'] } }, + options: [ + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + placeholder: 'name@email.com', + displayOptions: { show: { resource: ['utility'], operation: ['prefetchBrandDataByEmail'] } }, + required: true, + default: "", + description: 'Email address to prefetch brand data for. The domain will be extracted from the email. Free email providers (gmail.com, yahoo.com, etc.) and disposable email addresses are not allowed.', + routing: { send: { property: 'email', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['utility'], operation: ['prefetchBrandDataByEmail'] } }, options: [ { - name: 'Prefetch by Domain', - value: 'prefetch', - action: 'Warm cache for a domain before retrieval', - description: 'Signal that you will fetch brand data for this domain soon to reduce latency', - routing: { request: { method: 'POST', url: '/brand/prefetch' } }, + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, }, { - name: 'Prefetch by Email', - value: 'prefetchByEmail', - action: 'Warm cache for a domain via email address', - description: - 'Same as prefetch but accepts an email — extracts and validates the domain. Free/disposable emails are rejected.', - routing: { request: { method: 'POST', url: '/brand/prefetch-by-email' } }, + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, }, ], - default: 'prefetch', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + displayOptions: { show: { resource: ['utility'], operation: ['prefetchData'] } }, + required: true, + default: "brand", + options: [{ name: 'Brand', value: "brand" }, { name: 'Styleguide', value: "styleguide" }], + description: 'What to prefetch: \'brand\' warms the brand data cache, \'styleguide\' warms the styleguide cache', + routing: { send: { property: 'type', type: 'body' } }, }, { displayName: 'Domain', - name: 'domain', + name: 'identifierDomain', type: 'string', + displayOptions: { show: { resource: ['utility'], operation: ['prefetchData'] } }, required: true, - displayOptions: { show: { ...show, operation: ['prefetch'] } }, - default: '', - placeholder: 'stripe.com', - routing: { request: { body: { domain: '={{ $value }}' } } }, + default: "", + description: 'Domain name to prefetch data for', + routing: { send: { property: 'identifier.domain', type: 'body' } }, }, { displayName: 'Email', - name: 'email', + name: 'identifierEmail', type: 'string', + displayOptions: { show: { resource: ['utility'], operation: ['prefetchData'] } }, required: true, - displayOptions: { show: { ...show, operation: ['prefetchByEmail'] } }, - default: '', - placeholder: 'contact@stripe.com', - description: - 'Free email providers (gmail.com, yahoo.com) and disposable addresses are not allowed', - routing: { request: { body: { email: '={{ $value }}' } } }, + default: "", + description: 'Email address to prefetch data for. The domain will be extracted from the email. Free email providers (gmail.com, yahoo.com, etc.) and disposable email addresses are not allowed.', + routing: { send: { property: 'identifier.email', type: 'body' } }, }, { displayName: 'Additional Fields', @@ -56,15 +126,24 @@ export const utilityDescription: INodeProperties[] = [ type: 'collection', placeholder: 'Add Field', default: {}, - displayOptions: { show: { ...show, operation: ['prefetch', 'prefetchByEmail'] } }, + displayOptions: { show: { resource: ['utility'], operation: ['prefetchData'] } }, options: [ { - displayName: 'Timeout (Ms)', + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', name: 'timeoutMS', type: 'number', - default: 30000, - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, }, ], }, diff --git a/nodes/ContextDev/resources/web/brand.ts b/nodes/ContextDev/resources/web/brand.ts deleted file mode 100644 index 1077287..0000000 --- a/nodes/ContextDev/resources/web/brand.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const show = { resource: ['web'] }; -const visualOps = ['screenshot', 'extractStyleguide', 'extractFonts', 'extractCompetitors']; - -export const brandFields: INodeProperties[] = [ - { - // The SDK accepts EITHER domain OR directUrl (not both), so domain is not required — - // an empty value is omitted so a directUrl-only request is possible. - displayName: 'Domain', - name: 'domain', - type: 'string', - displayOptions: { show: { ...show, operation: visualOps } }, - default: '', - placeholder: 'stripe.com', - description: 'Company domain. Provide either Domain or Direct URL (in Additional Fields), not both.', - routing: { request: { qs: { domain: '={{ $value || undefined }}' } } }, - }, - - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { ...show, operation: visualOps } }, - options: [ - { - displayName: 'Color Scheme', - name: 'colorScheme', - type: 'options', - displayOptions: { show: { '/operation': ['screenshot', 'extractStyleguide'] } }, - default: '', - options: [ - { name: 'Auto (Default)', value: '' }, - { name: 'Light', value: 'light' }, - { name: 'Dark', value: 'dark' }, - ], - description: 'Preferred color scheme to request from the target page', - routing: { request: { qs: { colorScheme: '={{ $value }}' } } }, - }, - { - displayName: 'Country', - name: 'country', - type: 'string', - displayOptions: { show: { '/operation': ['screenshot'] } }, - default: '', - placeholder: 'us', - description: - 'Two-letter ISO 3166-1 alpha-2 country code to fetch the page from that location', - routing: { request: { qs: { country: '={{ $value }}' } } }, - }, - { - displayName: 'Direct URL', - name: 'directUrl', - type: 'string', - displayOptions: { - show: { - '/operation': ['screenshot', 'extractStyleguide', 'extractFonts'], - }, - }, - default: '', - placeholder: 'https://example.com/design-system', - description: 'Fetch from this exact URL instead of resolving from the domain. Alternative to Domain (not both).', - routing: { request: { qs: { directUrl: '={{ $value || undefined }}' } } }, - }, - { - displayName: 'Full Page Screenshot', - name: 'fullScreenshot', - type: 'boolean', - displayOptions: { show: { '/operation': ['screenshot'] } }, - default: false, - description: 'Whether to capture the full scrollable page height instead of just the visible viewport', - routing: { request: { qs: { fullScreenshot: '={{ $value ? "true" : "false" }}' } } }, - }, - { - displayName: 'Handle Cookie Popup', - name: 'handleCookiePopup', - type: 'boolean', - displayOptions: { show: { '/operation': ['screenshot'] } }, - default: false, - description: 'Whether to attempt to dismiss cookie consent banners before taking the screenshot', - routing: { request: { qs: { handleCookiePopup: '={{ $value ? "true" : "false" }}' } } }, - }, - { - displayName: 'Max Age (Ms)', - name: 'maxAgeMs', - type: 'number', - displayOptions: { show: { '/operation': ['screenshot', 'extractStyleguide', 'extractFonts'] } }, - default: 86400000, - description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', - routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, - }, - { - displayName: 'Number of Competitors', - name: 'numCompetitors', - type: 'number', - displayOptions: { show: { '/operation': ['extractCompetitors'] } }, - default: 5, - typeOptions: { minValue: 1 }, - routing: { request: { qs: { numCompetitors: '={{ $value }}' } } }, - }, - { - displayName: 'Page Type', - name: 'page', - type: 'options', - displayOptions: { show: { '/operation': ['screenshot'] } }, - default: '', - options: [ - { name: 'Blog', value: 'blog' }, - { name: 'Careers', value: 'careers' }, - { name: 'Contact', value: 'contact' }, - { name: 'Homepage (Default)', value: '' }, - { name: 'Login', value: 'login' }, - { name: 'Pricing', value: 'pricing' }, - { name: 'Privacy', value: 'privacy' }, - { name: 'Signup', value: 'signup' }, - { name: 'Terms', value: 'terms' }, - ], - routing: { request: { qs: { page: '={{ $value }}' } } }, - }, - { - displayName: 'Scroll Offset', - name: 'scrollOffset', - type: 'number', - displayOptions: { show: { '/operation': ['screenshot'] } }, - default: 0, - description: - 'Vertical scroll offset in pixels to capture a long page in viewport-sized slices. Takes precedence over Full Page Screenshot. Max 100,000.', - typeOptions: { minValue: 0, maxValue: 100000 }, - routing: { request: { qs: { scrollOffset: '={{ $value }}' } } }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails', - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, - }, - { - displayName: 'Viewport', - name: 'viewport', - type: 'collection', - placeholder: 'Add Dimension', - default: {}, - displayOptions: { show: { '/operation': ['screenshot'] } }, - description: 'Browser viewport dimensions for the screenshot. Defaults to 1920x1080.', - options: [ - { - displayName: 'Width', - name: 'width', - type: 'number', - default: 1920, - description: 'Viewport width in pixels', - typeOptions: { minValue: 240, maxValue: 7680 }, - }, - { - displayName: 'Height', - name: 'height', - type: 'number', - default: 1080, - description: 'Viewport height in pixels', - typeOptions: { minValue: 240, maxValue: 4320 }, - }, - ], - routing: { request: { qs: { viewport: '={{ $value }}' } } }, - }, - { - displayName: 'Wait For (Ms)', - name: 'waitForMs', - type: 'number', - displayOptions: { show: { '/operation': ['screenshot'] } }, - default: 0, - description: 'Delay capturing by this many milliseconds after page load. Max 30,000 ms.', - typeOptions: { minValue: 0, maxValue: 30000 }, - routing: { request: { qs: { waitForMs: '={{ $value }}' } } }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/resources/web/index.ts b/nodes/ContextDev/resources/web/index.ts deleted file mode 100644 index 0aa3d3f..0000000 --- a/nodes/ContextDev/resources/web/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; -import { scrapingFields } from './scraping'; -import { brandFields } from './brand'; -import { searchExtractFields } from './searchExtract'; - -const show = { resource: ['web'] }; - -const operationField: INodeProperties = { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { show }, - options: [ - { - name: 'Crawl Sitemap', - value: 'scrapeSitemap', - action: 'Crawl sitemap and return all discovered urls', - description: - 'Parse a domain sitemap and return all discovered page URLs. View docs.', - routing: { request: { method: 'GET', url: '/web/scrape/sitemap' } }, - }, - { - name: 'Crawl Website', - value: 'crawl', - action: 'Crawl a website and return markdown for all pages', - description: - 'Crawl multiple pages of a website and return markdown for each. View docs.', - routing: { request: { method: 'POST', url: '/web/crawl' } }, - }, - { - name: 'Extract Competitors', - value: 'extractCompetitors', - action: 'Extract competitors for a domain', - description: 'Identify direct competitors for a given brand domain', - routing: { request: { method: 'GET', url: '/web/competitors' } }, - }, - { - name: 'Extract Fonts', - value: 'extractFonts', - action: 'Extract font information from a website', - description: - 'Extract font families, usage statistics, fallbacks, and element counts. View docs.', - routing: { request: { method: 'GET', url: '/web/fonts' } }, - }, - { - name: 'Extract Structured Data', - value: 'extract', - action: 'Extract structured data using a JSON schema', - description: - 'Crawl a website and extract data matching your JSON schema. View docs.', - routing: { request: { method: 'POST', url: '/web/extract' } }, - }, - { - name: 'Extract Styleguide', - value: 'extractStyleguide', - action: 'Extract design system from a website', - description: - 'Extract colors, typography, spacing, shadows, and UI components. View docs.', - routing: { request: { method: 'GET', url: '/web/styleguide' } }, - }, - { - name: 'Scrape HTML', - value: 'scrapeHtml', - action: 'Scrape a URL and return raw HTML', - description: - 'Scrape any URL and return the raw HTML. View docs.', - routing: { request: { method: 'GET', url: '/web/scrape/html' } }, - }, - { - name: 'Scrape Images', - value: 'scrapeImages', - action: 'Extract all images from a webpage', - description: - 'Extract image assets from a web page including URLs, inline SVGs, and metadata. View docs.', - routing: { request: { method: 'GET', url: '/web/scrape/images' } }, - }, - { - name: 'Scrape to Markdown', - value: 'scrapeMd', - action: 'Scrape a url to markdown', - description: - 'Scrape any URL and get back clean markdown content. View docs.', - routing: { request: { method: 'GET', url: '/web/scrape/markdown' } }, - }, - { - name: 'Screenshot', - value: 'screenshot', - action: 'Take a screenshot of a website', - description: - 'Capture a screenshot with options for page type and viewport. View docs.', - routing: { request: { method: 'GET', url: '/web/screenshot' } }, - }, - { - name: 'Search', - value: 'search', - action: 'Search the web and return results', - description: - 'Run a web search and get structured results with optional markdown content. View docs.', - routing: { request: { method: 'POST', url: '/web/search' } }, - }, - ], - default: 'scrapeMd', -}; - -export const webDescription: INodeProperties[] = [ - operationField, - ...scrapingFields, // scrapeMd, scrapeHtml, scrapeImages, crawl, scrapeSitemap - ...brandFields, // screenshot, extractStyleguide, extractFonts, extractCompetitors - ...searchExtractFields, // search, extract -]; diff --git a/nodes/ContextDev/resources/web/scraping.ts b/nodes/ContextDev/resources/web/scraping.ts deleted file mode 100644 index eedf2c1..0000000 --- a/nodes/ContextDev/resources/web/scraping.ts +++ /dev/null @@ -1,500 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const show = { resource: ['web'] }; -const scrapingOps = ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'crawl', 'scrapeSitemap']; - -export const scrapingFields: INodeProperties[] = [ - { - displayName: 'URL', - name: 'url', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['scrapeMd', 'scrapeHtml', 'scrapeImages'] } }, - default: '', - placeholder: 'https://stripe.com/docs', - routing: { request: { qs: { url: '={{ $value }}' } } }, - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['crawl'] } }, - default: '', - placeholder: 'https://stripe.com/blog', - routing: { request: { body: { url: '={{ $value }}' } } }, - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['scrapeSitemap'] } }, - default: '', - placeholder: 'stripe.com', - routing: { request: { qs: { domain: '={{ $value }}' } } }, - }, - - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { ...show, operation: scrapingOps } }, - options: [ - { - displayName: 'Country', - name: 'country', - type: 'string', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: '', - placeholder: 'us', - description: - 'Two-letter ISO 3166-1 alpha-2 country code to fetch the page from that location', - routing: { request: { qs: { country: '={{ $value }}' } } }, - }, - { - displayName: 'Country', - name: 'countryPost', - type: 'string', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: '', - placeholder: 'us', - description: - 'Two-letter ISO 3166-1 alpha-2 country code to fetch pages from that location', - routing: { request: { body: { country: '={{ $value }}' } } }, - }, - { - displayName: 'Custom Headers', - name: 'headers', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - default: {}, - displayOptions: { - show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, - }, - description: - 'Custom HTTP headers forwarded to the target URL. Sending headers bypasses the cache.', - options: [ - { - name: 'header', - displayName: 'Header', - values: [ - { displayName: 'Name', name: 'name', type: 'string', default: '' }, - { displayName: 'Value', name: 'value', type: 'string', default: '' }, - ], - }, - ], - routing: { - request: { - qs: { - headers: - '={{ Object.fromEntries(($value.header || []).map(h => [h.name, h.value])) }}', - }, - }, - }, - }, - { - displayName: 'Enrichment', - name: 'enrichment', - type: 'collection', - placeholder: 'Add Enrichment Option', - default: {}, - displayOptions: { show: { '/operation': ['scrapeImages'] } }, - description: - 'Enrich each returned image. Enabling enrichment raises the call cost to 5 credits.', - options: [ - { - displayName: 'Classification', - name: 'classification', - type: 'boolean', - default: false, - description: 'Whether to classify each image by visual asset type', - }, - { - displayName: 'Hosted URL', - name: 'hostedUrl', - type: 'boolean', - default: false, - description: - 'Whether to host materializable images on the CDN and return their URL and MIME type', - }, - { - displayName: 'Max Time per Image (Ms)', - name: 'maxTimePerMs', - type: 'number', - default: 30000, - description: 'Per-image enrichment timeout in milliseconds. Default 30,000. Max 60,000.', - typeOptions: { minValue: 1, maxValue: 60000 }, - }, - { - displayName: 'Resolution', - name: 'resolution', - type: 'boolean', - default: false, - description: 'Whether to measure image width and height when possible', - }, - ], - routing: { request: { qs: { enrichment: '={{ $value }}' } } }, - }, - { - displayName: 'Exclude Selectors', - name: 'excludeSelectors', - type: 'string', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: '', - description: 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner")', - routing: { - request: { - qs: { excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, - arrayFormat: 'comma', - }, - }, - }, - { - displayName: 'Exclude Selectors', - name: 'excludeSelectorsPost', - type: 'string', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: '', - description: 'Comma-separated CSS selectors to remove before extraction (e.g. "nav, footer, .ad-banner")', - routing: { - request: { - body: { - excludeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', - }, - }, - }, - }, - { - displayName: 'Follow Subdomains', - name: 'followSubdomains', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: false, - description: 'Whether to follow links to subdomains (e.g. blog.example.com) during a crawl', - routing: { request: { body: { followSubdomains: '={{ $value }}' } } }, - }, - { - displayName: 'Include Frames', - name: 'includeFrames', - type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: false, - description: 'Whether to include content from iframes embedded in the page', - routing: { request: { qs: { includeFrames: '={{ $value }}' } } }, - }, - { - displayName: 'Include Frames', - name: 'includeFramesPost', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: false, - description: 'Whether to include content from iframes embedded in the page', - routing: { request: { body: { includeFrames: '={{ $value }}' } } }, - }, - { - displayName: 'Include Images', - name: 'includeImages', - type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd'] } }, - default: false, - description: 'Whether to include image references in the returned Markdown output', - routing: { request: { qs: { includeImages: '={{ $value }}' } } }, - }, - { - displayName: 'Include Images', - name: 'includeImagesPost', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: false, - description: 'Whether to include image references in the returned Markdown output', - routing: { request: { body: { includeImages: '={{ $value }}' } } }, - }, - { - displayName: 'Include Links', - name: 'includeLinks', - type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd'] } }, - default: true, - description: 'Whether to preserve hyperlinks in the returned Markdown output', - routing: { request: { qs: { includeLinks: '={{ $value }}' } } }, - }, - { - displayName: 'Include Links', - name: 'includeLinksPost', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: true, - description: 'Whether to preserve hyperlinks in the returned Markdown output', - routing: { request: { body: { includeLinks: '={{ $value }}' } } }, - }, - { - displayName: 'Include Selectors', - name: 'includeSelectors', - type: 'string', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: '', - description: - 'Comma-separated CSS selectors. When set, only matching subtrees are kept (e.g. "article.main, #content").', - routing: { - request: { - qs: { includeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, - arrayFormat: 'comma', - }, - }, - }, - { - displayName: 'Include Selectors', - name: 'includeSelectorsPost', - type: 'string', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: '', - description: - 'Comma-separated CSS selectors. When set, only matching subtrees are kept (e.g. "article.main, #content").', - routing: { - request: { - body: { - includeSelectors: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}', - }, - }, - }, - }, - { - displayName: 'Main Content Only', - name: 'useMainContentOnly', - type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - default: false, - description: 'Whether to strip navigation, headers, and footers — return only the main body content', - routing: { request: { qs: { useMainContentOnly: '={{ $value }}' } } }, - }, - { - displayName: 'Main Content Only', - name: 'useMainContentOnlyPost', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: false, - description: 'Whether to strip navigation, headers, and footers — return only the main body content', - routing: { request: { body: { useMainContentOnly: '={{ $value }}' } } }, - }, - { - displayName: 'Max Age (Ms)', - name: 'maxAgeMs', - type: 'number', - displayOptions: { - show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages'] }, - }, - default: 86400000, - description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', - routing: { request: { qs: { maxAgeMs: '={{ $value }}' } } }, - }, - { - displayName: 'Max Age (Ms)', - name: 'maxAgeMsPost', - type: 'number', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: 86400000, - description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', - routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, - }, - { - displayName: 'Max Depth', - name: 'maxDepth', - type: 'number', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: 3, - description: - 'How many link levels deep to crawl from the starting URL. 0 = starting page only.', - typeOptions: { minValue: 0 }, - routing: { request: { body: { maxDepth: '={{ $value }}' } } }, - }, - { - displayName: 'Max Links', - name: 'maxLinks', - type: 'number', - displayOptions: { show: { '/operation': ['scrapeSitemap'] } }, - default: 10000, - description: 'Maximum number of URLs to return from the sitemap. Capped at 100,000.', - typeOptions: { minValue: 1, maxValue: 100000 }, - routing: { request: { qs: { maxLinks: '={{ $value }}' } } }, - }, - { - displayName: 'Max Pages', - name: 'maxPages', - type: 'number', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: 10, - description: 'Maximum number of pages to crawl. Increase for larger sites. Hard cap: 500.', - typeOptions: { minValue: 1, maxValue: 500 }, - routing: { request: { body: { maxPages: '={{ $value }}' } } }, - }, - { - displayName: 'PDF Parsing', - name: 'pdf', - type: 'collection', - placeholder: 'Add PDF Option', - default: {}, - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml'] } }, - description: 'Control how linked PDF documents are fetched and parsed', - options: [ - { - displayName: 'End Page', - name: 'end', - type: 'number', - default: 1, - description: 'Last 1-based PDF page to parse. Must be greater than or equal to Start Page.', - typeOptions: { minValue: 1 }, - }, - { - displayName: 'Should Parse', - name: 'shouldParse', - type: 'boolean', - default: true, - description: - 'Whether PDF pages are fetched and parsed. When false, PDF pages are skipped.', - }, - { - displayName: 'Start Page', - name: 'start', - type: 'number', - default: 1, - description: 'First 1-based PDF page to parse', - typeOptions: { minValue: 1 }, - }, - ], - routing: { request: { qs: { pdf: '={{ $value }}' } } }, - }, - { - displayName: 'PDF Parsing', - name: 'pdfPost', - type: 'collection', - placeholder: 'Add PDF Option', - default: {}, - displayOptions: { show: { '/operation': ['crawl'] } }, - description: 'Control how linked PDF documents are fetched and parsed', - options: [ - { - displayName: 'End Page', - name: 'end', - type: 'number', - default: 1, - description: 'Last 1-based PDF page to parse. Must be greater than or equal to Start Page.', - typeOptions: { minValue: 1 }, - }, - { - displayName: 'Should Parse', - name: 'shouldParse', - type: 'boolean', - default: true, - description: - 'Whether PDF pages are fetched and parsed. When false, PDF pages are skipped.', - }, - { - displayName: 'Start Page', - name: 'start', - type: 'number', - default: 1, - description: 'First 1-based PDF page to parse', - typeOptions: { minValue: 1 }, - }, - ], - routing: { request: { body: { pdf: '={{ $value }}' } } }, - }, - { - displayName: 'Shorten Base64 Images', - name: 'shortenBase64Images', - type: 'boolean', - displayOptions: { show: { '/operation': ['scrapeMd'] } }, - default: false, - description: 'Whether to truncate inline base64 image payloads to keep responses small', - routing: { request: { qs: { shortenBase64Images: '={{ $value }}' } } }, - }, - { - displayName: 'Shorten Base64 Images', - name: 'shortenBase64ImagesPost', - type: 'boolean', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: false, - description: 'Whether to truncate inline base64 image payloads to keep responses small', - routing: { request: { body: { shortenBase64Images: '={{ $value }}' } } }, - }, - { - displayName: 'Stop After (Ms)', - name: 'stopAfterMs', - type: 'number', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: 80000, - description: - 'Stop the crawl after this many milliseconds, returning pages collected so far. Between 10,000 and 110,000 ms.', - typeOptions: { minValue: 10000, maxValue: 110000 }, - routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - displayOptions: { - show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages', 'scrapeSitemap'] }, - }, - default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails', - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { qs: { timeoutMS: '={{ $value }}' } } }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMSPost', - type: 'number', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails', - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, - }, - { - displayName: 'URL Regex', - name: 'urlRegex', - type: 'string', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: '', - placeholder: '^https?://[^/]+/blog/', - description: 'Only crawl URLs matching this RE2 regex pattern', - routing: { request: { body: { urlRegex: '={{ $value }}' } } }, - }, - { - displayName: 'URL Regex', - name: 'urlRegexSitemap', - type: 'string', - displayOptions: { show: { '/operation': ['scrapeSitemap'] } }, - default: '', - placeholder: '^https?://[^/]+/blog/', - description: 'Only return sitemap URLs matching this RE2 regex pattern', - routing: { request: { qs: { urlRegex: '={{ $value }}' } } }, - }, - { - displayName: 'Wait For (Ms)', - name: 'waitForMs', - type: 'number', - displayOptions: { show: { '/operation': ['scrapeMd', 'scrapeHtml', 'scrapeImages'] } }, - default: 0, - description: - 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', - typeOptions: { minValue: 0, maxValue: 30000 }, - routing: { request: { qs: { waitForMs: '={{ $value }}' } } }, - }, - { - displayName: 'Wait For (Ms)', - name: 'waitForMsPost', - type: 'number', - displayOptions: { show: { '/operation': ['crawl'] } }, - default: 0, - description: - 'Delay scraping by this many milliseconds after page load — useful for pages with delayed JavaScript rendering. Max 30,000 ms.', - typeOptions: { minValue: 0, maxValue: 30000 }, - routing: { request: { body: { waitForMs: '={{ $value }}' } } }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/resources/web/searchExtract.ts b/nodes/ContextDev/resources/web/searchExtract.ts deleted file mode 100644 index 8113a96..0000000 --- a/nodes/ContextDev/resources/web/searchExtract.ts +++ /dev/null @@ -1,377 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -const show = { resource: ['web'] }; -const searchExtractOps = ['search', 'extract']; - -export const searchExtractFields: INodeProperties[] = [ - { - displayName: 'Query', - name: 'query', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['search'] } }, - default: '', - placeholder: 'best CRM software 2025', - routing: { request: { body: { query: '={{ $value }}' } } }, - }, - { - displayName: 'Schema (JSON)', - name: 'schema', - type: 'json', - required: true, - displayOptions: { show: { ...show, operation: ['extract'] } }, - default: '{}', - description: 'JSON Schema describing the shape of data to extract from the website', - routing: { request: { body: { schema: '={{ JSON.parse($value) }}' } } }, - }, - { - displayName: 'URL', - name: 'url', - type: 'string', - required: true, - displayOptions: { show: { ...show, operation: ['extract'] } }, - default: '', - placeholder: 'https://stripe.com', - routing: { request: { body: { url: '={{ $value }}' } } }, - }, - - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { show: { ...show, operation: searchExtractOps } }, - options: [ - { - displayName: 'Country', - name: 'country', - type: 'string', - displayOptions: { show: { '/operation': ['search'] } }, - default: '', - placeholder: 'us', - description: - 'Two-letter ISO 3166-1 alpha-2 country code to run the search from that location', - routing: { request: { body: { country: '={{ $value }}' } } }, - }, - { - displayName: 'Exclude Domains', - name: 'excludeDomains', - type: 'string', - displayOptions: { show: { '/operation': ['search'] } }, - default: '', - placeholder: 'pinterest.com,reddit.com', - description: 'Comma-separated blocklist of domains to exclude from results', - routing: { - request: { - body: { excludeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, - }, - }, - }, - { - displayName: 'Fact Check', - name: 'factCheck', - type: 'boolean', - displayOptions: { show: { '/operation': ['extract'] } }, - default: false, - description: 'Whether to require every returned value to be grounded in facts stated on the page', - routing: { request: { body: { factCheck: '={{ $value }}' } } }, - }, - { - displayName: 'Follow Subdomains', - name: 'followSubdomains', - type: 'boolean', - displayOptions: { show: { '/operation': ['extract'] } }, - default: false, - description: 'Whether to follow links to subdomains during the extraction crawl', - routing: { request: { body: { followSubdomains: '={{ $value }}' } } }, - }, - { - displayName: 'Freshness', - name: 'freshness', - type: 'options', - displayOptions: { show: { '/operation': ['search'] } }, - default: '', - options: [ - { name: 'Any Time', value: '' }, - { name: 'Last 24 Hours', value: 'last_24_hours' }, - { name: 'Last Month', value: 'last_month' }, - { name: 'Last Week', value: 'last_week' }, - { name: 'Last Year', value: 'last_year' }, - ], - routing: { request: { body: { freshness: '={{ $value }}' } } }, - }, - { - displayName: 'Include Domains', - name: 'includeDomains', - type: 'string', - displayOptions: { show: { '/operation': ['search'] } }, - default: '', - placeholder: 'arxiv.org,github.com', - description: 'Comma-separated allowlist — only return results from these domains', - routing: { - request: { - body: { includeDomains: '={{ $value.split(",").map(s => s.trim()).filter(Boolean) }}' }, - }, - }, - }, - { - displayName: 'Include Frames', - name: 'includeFrames', - type: 'boolean', - displayOptions: { show: { '/operation': ['extract'] } }, - default: false, - description: 'Whether to include content from iframes embedded in the page', - routing: { request: { body: { includeFrames: '={{ $value }}' } } }, - }, - { - displayName: 'Instructions', - name: 'instructions', - type: 'string', - displayOptions: { show: { '/operation': ['extract'] } }, - default: '', - description: - 'Optional guidance on which facts to prioritize or how to interpret schema fields', - routing: { request: { body: { instructions: '={{ $value }}' } } }, - }, - { - displayName: 'Markdown: Include Frames', - name: 'markdownIncludeFrames', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: false, - description: 'Whether to render iframe contents into the Markdown', - routing: { send: { type: 'body', property: 'markdownOptions.includeFrames' } }, - }, - { - displayName: 'Markdown: Include Images', - name: 'markdownIncludeImages', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: false, - description: 'Whether to emit image references in the Markdown', - routing: { send: { type: 'body', property: 'markdownOptions.includeImages' } }, - }, - { - displayName: 'Markdown: Include Links', - name: 'markdownIncludeLinks', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: false, - description: 'Whether to keep hyperlinks in the Markdown', - routing: { send: { type: 'body', property: 'markdownOptions.includeLinks' } }, - }, - { - displayName: 'Markdown: Max Age (Ms)', - name: 'markdownMaxAgeMs', - type: 'number', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: 86400000, - description: - 'Cache TTL in ms for scraped Markdown keyed by URL + options. Default 1 day, max 30 days. Set to 0 to force a fresh scrape.', - routing: { send: { type: 'body', property: 'markdownOptions.maxAgeMs' } }, - }, - { - displayName: 'Markdown: PDF Parsing', - name: 'markdownPdf', - type: 'collection', - placeholder: 'Add PDF Option', - default: {}, - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - description: - 'Control how PDF result pages are fetched and parsed when scraping search results to Markdown', - options: [ - { - displayName: 'End Page', - name: 'end', - type: 'number', - default: 1, - description: 'Last PDF page to parse (1-based, inclusive). Defaults to the final page. Must be >= Start Page.', - typeOptions: { minValue: 1 }, - }, - { - displayName: 'Should Parse', - name: 'shouldParse', - type: 'boolean', - default: true, - description: - 'Whether PDF URLs are parsed. When false, PDF results are skipped with WEBSITE_ACCESS_ERROR.', - }, - { - displayName: 'Start Page', - name: 'start', - type: 'number', - default: 1, - description: 'First PDF page to parse (1-based, inclusive). Defaults to page 1.', - typeOptions: { minValue: 1 }, - }, - ], - routing: { send: { type: 'body', property: 'markdownOptions.pdf' } }, - }, - { - displayName: 'Markdown: Shorten Base64 Images', - name: 'markdownShortenBase64Images', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: false, - description: 'Whether to truncate inline base64 image payloads to keep responses small', - routing: { send: { type: 'body', property: 'markdownOptions.shortenBase64Images' } }, - }, - { - displayName: 'Markdown: Timeout (Ms)', - name: 'markdownTimeoutMS', - type: 'number', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: 30000, - description: - 'Maximum time in milliseconds to wait for each result scrape before it is aborted', - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { send: { type: 'body', property: 'markdownOptions.timeoutMS' } }, - }, - { - displayName: 'Markdown: Use Main Content Only', - name: 'markdownUseMainContentOnly', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: false, - description: 'Whether to strip nav, header, footer, and sidebar — keep only the primary article content', - routing: { send: { type: 'body', property: 'markdownOptions.useMainContentOnly' } }, - }, - { - displayName: 'Markdown: Wait For (Ms)', - name: 'markdownWaitForMs', - type: 'number', - displayOptions: { show: { '/operation': ['search'], '/markdownEnabled': [true] } }, - default: 0, - description: 'Extra wait after page load before rendering, in ms. Useful for JS-heavy pages.', - typeOptions: { minValue: 0, maxValue: 30000 }, - routing: { send: { type: 'body', property: 'markdownOptions.waitForMs' } }, - }, - { - displayName: 'Max Age (Ms)', - name: 'maxAgeMs', - type: 'number', - displayOptions: { show: { '/operation': ['extract'] } }, - default: 86400000, - description: 'Max age of cached result in ms before a fresh fetch. Default 1 day.', - routing: { request: { body: { maxAgeMs: '={{ $value }}' } } }, - }, - { - displayName: 'Max Depth', - name: 'maxDepth', - type: 'number', - displayOptions: { show: { '/operation': ['extract'] } }, - default: 3, - description: - 'Maximum link depth to crawl from the starting URL during extraction. 0 = starting page only.', - typeOptions: { minValue: 0 }, - routing: { request: { body: { maxDepth: '={{ $value }}' } } }, - }, - { - displayName: 'Max Pages', - name: 'maxPages', - type: 'number', - displayOptions: { show: { '/operation': ['extract'] } }, - default: 5, - description: 'Maximum number of pages to analyze for extraction. Hard cap: 50.', - typeOptions: { minValue: 1, maxValue: 50 }, - routing: { request: { body: { maxPages: '={{ $value }}' } } }, - }, - { - displayName: 'Number of Results', - name: 'numResults', - type: 'number', - displayOptions: { show: { '/operation': ['search'] } }, - default: 10, - description: 'Number of results to request and return. Between 10 and 100.', - typeOptions: { minValue: 10, maxValue: 100 }, - routing: { request: { body: { numResults: '={{ $value }}' } } }, - }, - { - displayName: 'PDF Parsing', - name: 'pdf', - type: 'collection', - placeholder: 'Add PDF Option', - default: {}, - displayOptions: { show: { '/operation': ['extract'] } }, - description: 'Control how linked PDF documents are fetched and parsed', - options: [ - { - displayName: 'End Page', - name: 'end', - type: 'number', - default: 1, - description: 'Last 1-based PDF page to parse. Must be greater than or equal to Start Page.', - typeOptions: { minValue: 1 }, - }, - { - displayName: 'Should Parse', - name: 'shouldParse', - type: 'boolean', - default: true, - description: - 'Whether PDF pages are fetched and parsed. When false, PDF pages are skipped.', - }, - { - displayName: 'Start Page', - name: 'start', - type: 'number', - default: 1, - description: 'First 1-based PDF page to parse', - typeOptions: { minValue: 1 }, - }, - ], - routing: { request: { body: { pdf: '={{ $value }}' } } }, - }, - { - displayName: 'Query Fanout', - name: 'queryFanout', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'] } }, - default: false, - description: 'Whether to expand the query into multiple parallel variants for broader recall', - routing: { request: { body: { queryFanout: '={{ $value }}' } } }, - }, - { - displayName: 'Scrape Results to Markdown', - name: 'markdownEnabled', - type: 'boolean', - displayOptions: { show: { '/operation': ['search'] } }, - default: false, - description: - 'Whether to scrape each search result page and include the full content as Markdown. Significantly increases response time.', - routing: { send: { type: 'body', property: 'markdownOptions.enabled' } }, - }, - { - displayName: 'Stop After (Ms)', - name: 'stopAfterMs', - type: 'number', - displayOptions: { show: { '/operation': ['extract'] } }, - default: 80000, - description: - 'Stop the extraction crawl after this many milliseconds, returning data collected so far. Between 10,000 and 110,000 ms.', - typeOptions: { minValue: 10000, maxValue: 110000 }, - routing: { request: { body: { stopAfterMs: '={{ $value }}' } } }, - }, - { - displayName: 'Timeout (Ms)', - name: 'timeoutMS', - type: 'number', - default: 30000, - description: 'Maximum time in milliseconds to wait for a response before the request fails', - typeOptions: { minValue: 1000, maxValue: 300000 }, - routing: { request: { body: { timeoutMS: '={{ $value }}' } } }, - }, - { - displayName: 'Wait For (Ms)', - name: 'waitForMs', - type: 'number', - displayOptions: { show: { '/operation': ['extract'] } }, - default: 0, - description: 'Delay scraping by this many milliseconds after page load. Max 30,000 ms.', - typeOptions: { minValue: 0, maxValue: 30000 }, - routing: { request: { body: { waitForMs: '={{ $value }}' } } }, - }, - ], - }, -]; diff --git a/nodes/ContextDev/resources/webExtraction/index.ts b/nodes/ContextDev/resources/webExtraction/index.ts new file mode 100644 index 0000000..5988174 --- /dev/null +++ b/nodes/ContextDev/resources/webExtraction/index.ts @@ -0,0 +1,687 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const webExtractionDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['webExtraction'] } }, + options: [ + { name: 'Classify NAICS Industries', value: 'classifyNAICSIndustries', action: 'Classify NAICS industries', description: 'Classify any brand into 2022 NAICS industry codes from its domain or name', routing: { request: { method: 'GET', url: '/web/naics' } } }, + { name: 'Classify SIC Industries', value: 'classifySICIndustries', action: 'Classify SIC industries', description: 'Classify any brand into Standard Industrial Classification (SIC) codes from its domain or name. Choose between the original SIC system (`original_sic`) or the latest SIC list maintained by the SEC (`latest_sec`).', routing: { request: { method: 'GET', url: '/web/sic' } } }, + { name: 'Extract a Single Product From a URL', value: 'extractASingleProductFromAURL', action: 'Extract a single product from a URL', description: 'Given a single URL, determines if it is a product page and extracts the product information', routing: { request: { method: 'POST', url: '/brand/ai/product' } } }, + { name: 'Extract Products From a Brand\'s Website', value: 'extractProductsFromABrandSWebsite', action: 'Extract products from a brand s website', description: 'Extract product information from a brand\'s website. We will analyze the website and return a list of products with details such as name, description, image, pricing, features, and more.', routing: { request: { method: 'POST', url: '/brand/ai/products' } } }, + { name: 'Extract Structured Website Data', value: 'extractStructuredWebsiteData', action: 'Extract structured website data', description: 'Crawl a website, use the provided JSON Schema and instructions to prioritize relevant internal links, and extract structured data from the selected pages', routing: { request: { method: 'POST', url: '/web/extract' } } }, + { name: 'Find Website Competitors', value: 'findWebsiteCompetitors', action: 'Find website competitors', description: 'Analyze a company\'s landing page and web search evidence to return direct competitors for the same product or market', routing: { request: { method: 'GET', url: '/web/competitors' } } }, + { name: 'Query Website Data Using AI', value: 'queryWebsiteDataUsingAI', action: 'Query website data using AI', description: 'Use AI to extract specific data points from a brand\'s website. The AI will crawl the website and extract the requested information based on the provided data points.', routing: { request: { method: 'POST', url: '/brand/ai/query' } } }, + { name: 'Scrape Fonts', value: 'scrapeFonts', action: 'Scrape fonts', description: 'Scrape font information from a website including font families, usage statistics, fallbacks, and element/word counts', routing: { request: { method: 'GET', url: '/web/fonts' } } }, + { name: 'Scrape Styleguide', value: 'scrapeStyleguide', action: 'Scrape styleguide', description: 'Extract a comprehensive design system from a website including colors, typography, spacing, shadows, and UI components', routing: { request: { method: 'GET', url: '/web/styleguide' } } }, + ], + default: 'extractStructuredWebsiteData', + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractStructuredWebsiteData'] } }, + required: true, + default: "", + description: 'The starting website URL to crawl and extract from. Must include http:// or https://.', + routing: { send: { property: 'url', type: 'body' } }, + }, + { + displayName: 'Schema', + name: 'schema', + type: 'json', + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractStructuredWebsiteData'] } }, + required: true, + default: "{}", + description: 'JSON Schema for the returned data object. TypeScript Zod users can pass a JSON Schema generated from a Zod object; Python users can pass the equivalent JSON Schema object.', + routing: { send: { property: 'schema', type: 'body', value: '={{ $value ? JSON.parse($value) : undefined }}' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractStructuredWebsiteData'] } }, + options: [ + { + displayName: 'End', + name: 'pdfEnd', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last 1-based PDF page to parse. Must be greater than or equal to start when both are provided.', + routing: { send: { property: 'pdf.end', type: 'body' } }, + }, + { + displayName: 'Fact Check', + name: 'factCheck', + type: 'boolean', + default: false, + description: 'Whether true, every returned value must be grounded in facts stated on the page; fields that cannot be supported by the page are returned as null/empty. When false (default), the model may make reasonable inferences and derivations from the page content (e.g. ideal customer, competitor analysis, recommendations) while keeping verifiable specifics (names, quotes, URLs, dates, metrics) faithful to the source.', + routing: { send: { property: 'factCheck', type: 'body' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'followSubdomains', + type: 'boolean', + default: false, + description: 'Whether true, follow links on subdomains of the starting URL\'s domain', + routing: { send: { property: 'followSubdomains', type: 'body' } }, + }, + { + displayName: 'Include Frames', + name: 'includeFrames', + type: 'boolean', + default: false, + description: 'Whether true, iframe contents are included in Markdown before extraction', + routing: { send: { property: 'includeFrames', type: 'body' } }, + }, + { + displayName: 'Instructions', + name: 'instructions', + type: 'string', + default: "", + description: 'Optional extraction guidance, such as which facts to prioritize or how to interpret fields in the schema', + routing: { send: { property: 'instructions', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'number', + default: 604800000, + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return cached scrape results if a prior scrape for the same parameters is younger than this many milliseconds. Defaults to 7 days (604800000 ms).', + routing: { send: { property: 'maxAgeMs', type: 'body' } }, + }, + { + displayName: 'Max Depth', + name: 'maxDepth', + type: 'number', + default: 0, + typeOptions: {"minValue":0}, + description: 'Optional maximum link depth from the starting URL (0 = only the starting page). If omitted, there is no crawl depth limit.', + routing: { send: { property: 'maxDepth', type: 'body' } }, + }, + { + displayName: 'Max Pages', + name: 'maxPages', + type: 'number', + default: 5, + typeOptions: {"minValue":1,"maxValue":50}, + description: 'Maximum number of pages to analyze for extraction. Hard cap: 50. Defaults to 5.', + routing: { send: { property: 'maxPages', type: 'body' } }, + }, + { + displayName: 'Settle Animations', + name: 'settleAnimations', + type: 'boolean', + default: false, + description: 'Whether true, waits briefly for CSS and transition animations to settle before extracting each crawled page. Defaults to false. This adds a bit of latency in exchange for more stable output on animated pages.', + routing: { send: { property: 'settleAnimations', type: 'body' } }, + }, + { + displayName: 'Should Parse', + name: 'pdfShouldParse', + type: 'boolean', + default: true, + description: 'Whether true, PDF pages are fetched and parsed. When false, PDF pages are skipped.', + routing: { send: { property: 'pdf.shouldParse', type: 'body' } }, + }, + { + displayName: 'Start', + name: 'pdfStart', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First 1-based PDF page to parse', + routing: { send: { property: 'pdf.start', type: 'body' } }, + }, + { + displayName: 'Stop After MS', + name: 'stopAfterMs', + type: 'number', + default: 80000, + typeOptions: {"minValue":10000,"maxValue":110000}, + description: 'Soft time budget for the crawl in milliseconds. Min: 10000 (10s). Max: 110000 (110s). Default: 80000 (80s).', + routing: { send: { property: 'stopAfterMs', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + { + displayName: 'Wait for MS', + name: 'waitForMs', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Optional browser wait time in milliseconds after initial page load for each crawled page', + routing: { send: { property: 'waitForMs', type: 'body' } }, + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['findWebsiteCompetitors'] } }, + required: true, + default: "", + description: 'Company domain to analyze, such as `stripe.com`. Full http(s) URLs are accepted and normalized to their domain.', + routing: { request: { qs: { "domain": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['findWebsiteCompetitors'] } }, + options: [ + { + displayName: 'Num Competitors', + name: 'numCompetitors', + type: 'number', + default: 5, + typeOptions: {"minValue":1,"maxValue":10}, + description: 'Exact number of direct competitors to return. Defaults to 5.', + routing: { request: { qs: { "numCompetitors": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['scrapeStyleguide'] } }, + options: [ + { + displayName: 'Color Scheme', + name: 'colorScheme', + type: 'options', + default: "light", + options: [{ name: 'Dark', value: "dark" }, { name: 'Light', value: "light" }], + description: 'Optional browser color scheme to emulate for websites that respond to prefers-color-scheme. This value is part of the styleguide cache key.', + routing: { request: { qs: { "colorScheme": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + default: "", + description: 'A specific URL to fetch the styleguide from directly, bypassing domain resolution (e.g., \'https://example.com/design-system\'). When provided, the styleguide is extracted from this exact URL. You must provide either \'domain\' or \'directUrl\', but not both.', + routing: { request: { qs: { "directUrl": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: "", + description: 'Domain name to extract styleguide from (e.g., \'example.com\', \'google.com\'). The domain will be automatically normalized and validated. You must provide either \'domain\' or \'directUrl\', but not both.', + routing: { request: { qs: { "domain": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['scrapeFonts'] } }, + options: [ + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + default: "", + description: 'A specific URL to fetch fonts from directly, bypassing domain resolution (e.g., \'https://example.com/design-system\'). When provided, fonts are extracted from this exact URL. You must provide either \'domain\' or \'directUrl\', but not both.', + routing: { request: { qs: { "directUrl": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: "", + description: 'Domain name to extract fonts from (e.g., \'example.com\', \'google.com\'). The domain will be automatically normalized and validated. You must provide either \'domain\' or \'directUrl\', but not both.', + routing: { request: { qs: { "domain": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + description: 'Maximum age in milliseconds for cached brand data before the API performs a hard refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 year.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Input', + name: 'input', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['classifyNAICSIndustries'] } }, + required: true, + default: "", + description: 'Brand domain or title to retrieve NAICS code for. If a valid domain is provided, it will be used for classification, otherwise, we will search for the brand using the provided title.', + routing: { request: { qs: { "input": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['classifyNAICSIndustries'] } }, + options: [ + { + displayName: 'Max Results', + name: 'maxResults', + type: 'number', + default: 5, + typeOptions: {"minValue":1,"maxValue":10}, + description: 'Maximum number of NAICS codes to return. Must be between 1 and 10. Defaults to 5.', + routing: { request: { qs: { "maxResults": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Min Results', + name: 'minResults', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":10}, + description: 'Minimum number of NAICS codes to return. Must be at least 1. Defaults to 1.', + routing: { request: { qs: { "minResults": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Input', + name: 'input', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['classifySICIndustries'] } }, + required: true, + default: "", + description: 'Brand domain or title to retrieve SIC code for. If a valid domain is provided, it will be used for classification, otherwise, we will search for the brand using the provided title.', + routing: { request: { qs: { "input": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['classifySICIndustries'] } }, + options: [ + { + displayName: 'Max Results', + name: 'maxResults', + type: 'number', + default: 5, + typeOptions: {"minValue":1,"maxValue":10}, + description: 'Maximum number of SIC codes to return. Must be between 1 and 10. Defaults to 5.', + routing: { request: { qs: { "maxResults": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Min Results', + name: 'minResults', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":10}, + description: 'Minimum number of SIC codes to return. Must be at least 1. Defaults to 1.', + routing: { request: { qs: { "minResults": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + default: "original_sic", + options: [{ name: 'Latest Sec', value: "latest_sec" }, { name: 'Original SIC', value: "original_sic" }], + description: 'Which SIC dataset to classify against. `original_sic` uses the 1987 Standard Industrial Classification system; `latest_sec` uses the current SIC list as published by the SEC. Defaults to `original_sic`.', + routing: { request: { qs: { "type": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['queryWebsiteDataUsingAI'] } }, + required: true, + default: "", + description: 'The domain name to analyze', + routing: { send: { property: 'domain', type: 'body' } }, + }, + { + displayName: 'Data to Extract', + name: 'dataToExtract', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['queryWebsiteDataUsingAI'] } }, + required: true, + default: "", + description: 'Array of data points to extract from the website', + routing: { send: { property: 'data_to_extract', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['queryWebsiteDataUsingAI'] } }, + options: [ + { + displayName: 'About Us', + name: 'specificPagesAboutUs', + type: 'boolean', + default: false, + description: 'Whether to analyze the about us page', + routing: { send: { property: 'specific_pages.about_us', type: 'body' } }, + }, + { + displayName: 'Blog', + name: 'specificPagesBlog', + type: 'boolean', + default: false, + description: 'Whether to analyze the blog', + routing: { send: { property: 'specific_pages.blog', type: 'body' } }, + }, + { + displayName: 'Careers', + name: 'specificPagesCareers', + type: 'boolean', + default: false, + description: 'Whether to analyze the careers page', + routing: { send: { property: 'specific_pages.careers', type: 'body' } }, + }, + { + displayName: 'Contact Us', + name: 'specificPagesContactUs', + type: 'boolean', + default: false, + description: 'Whether to analyze the contact us page', + routing: { send: { property: 'specific_pages.contact_us', type: 'body' } }, + }, + { + displayName: 'Faq', + name: 'specificPagesFaq', + type: 'boolean', + default: false, + description: 'Whether to analyze the FAQ page', + routing: { send: { property: 'specific_pages.faq', type: 'body' } }, + }, + { + displayName: 'Home Page', + name: 'specificPagesHomePage', + type: 'boolean', + default: false, + description: 'Whether to analyze the home page', + routing: { send: { property: 'specific_pages.home_page', type: 'body' } }, + }, + { + displayName: 'Pricing', + name: 'specificPagesPricing', + type: 'boolean', + default: false, + description: 'Whether to analyze the pricing page', + routing: { send: { property: 'specific_pages.pricing', type: 'body' } }, + }, + { + displayName: 'Privacy Policy', + name: 'specificPagesPrivacyPolicy', + type: 'boolean', + default: false, + description: 'Whether to analyze the privacy policy page', + routing: { send: { property: 'specific_pages.privacy_policy', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Terms and Conditions', + name: 'specificPagesTermsAndConditions', + type: 'boolean', + default: false, + description: 'Whether to analyze the terms and conditions page', + routing: { send: { property: 'specific_pages.terms_and_conditions', type: 'body' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractASingleProductFromAURL'] } }, + required: true, + default: "", + description: 'The product page URL to extract product data from', + routing: { send: { property: 'url', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractASingleProductFromAURL'] } }, + options: [ + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'number', + default: 604800000, + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 7 days (604800000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh.', + routing: { send: { property: 'maxAgeMs', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractProductsFromABrandSWebsite'] } }, + required: true, + default: "", + description: 'The domain name to analyze', + routing: { send: { property: 'domain', type: 'body' } }, + }, + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractProductsFromABrandSWebsite'] } }, + required: true, + default: "", + description: 'A specific URL to use directly as the starting point for extraction without domain resolution', + routing: { send: { property: 'directUrl', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webExtraction'], operation: ['extractProductsFromABrandSWebsite'] } }, + options: [ + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'number', + default: 604800000, + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 7 days (604800000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh.', + routing: { send: { property: 'maxAgeMs', type: 'body' } }, + }, + { + displayName: 'Max Products', + name: 'maxProducts', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":12}, + description: 'Maximum number of products to extract', + routing: { send: { property: 'maxProducts', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + ], + }, +]; diff --git a/nodes/ContextDev/resources/webScraping/index.ts b/nodes/ContextDev/resources/webScraping/index.ts new file mode 100644 index 0000000..51322ae --- /dev/null +++ b/nodes/ContextDev/resources/webScraping/index.ts @@ -0,0 +1,1160 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const webScrapingDescription: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['webScraping'] } }, + options: [ + { name: 'Crawl Sitemap', value: 'crawlSitemap', action: 'Crawl sitemap', description: 'Crawl an entire website\'s sitemap and return all discovered page URLs. Pass `search` to have the crawled sitemap filtered down to the pages about a phrase (for example `pricing and plans` or `api authentication docs`), most relevant first — a searched crawl scans the whole sitemap and costs 2 credits instead of 1.', routing: { request: { method: 'GET', url: '/web/scrape/sitemap' } } }, + { name: 'Crawl Website & Scrape Markdown', value: 'crawlWebsiteScrapeMarkdown', action: 'Crawl website scrape markdown', description: 'Performs a crawl starting from a given URL, extracts page content as Markdown, and returns results for all crawled pages', routing: { request: { method: 'POST', url: '/web/crawl' } } }, + { name: 'Scrape HTML', value: 'scrapeHTML', action: 'Scrape HTML', description: 'Scrapes the given URL and returns the raw HTML content of the page. The base request costs 1 credit; requests with browser actions cost 2 credits.', routing: { request: { method: 'GET', url: '/web/scrape/html' } } }, + { name: 'Scrape Images', value: 'scrapeImages', action: 'Scrape images', description: 'Extract image assets from a web page, including standard URLs, inline SVGs, data URIs, responsive image sources, metadata, CSS backgrounds, video posters, and embeds. The base request costs 1 credit, or 2 credits with browser actions. When enrichment is enabled, the entire call costs 5 credits, including requests that also use actions', routing: { request: { method: 'GET', url: '/web/scrape/images' } } }, + { name: 'Scrape Markdown', value: 'scrapeMarkdown', action: 'Scrape markdown', description: 'Scrapes the given URL into LLM usable Markdown. Inspect key_metadata on JSON responses from a recognized API key; use error_code to distinguish stable failure categories.', routing: { request: { method: 'GET', url: '/web/scrape/markdown' } } }, + { name: 'Scrape Screenshot', value: 'scrapeScreenshot', action: 'Scrape screenshot', description: 'Capture a screenshot of a website', routing: { request: { method: 'GET', url: '/web/screenshot' } } }, + { name: 'Web Search', value: 'webSearch', action: 'Web search', description: 'Search the web and optionally scrape each result to Markdown in one round-trip', routing: { request: { method: 'POST', url: '/web/search' } } }, + ], + default: 'scrapeHTML', + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeHTML'] } }, + required: true, + default: "", + description: 'Full URL to scrape (must include http:// or https:// protocol)', + routing: { request: { qs: { "url": '={{ $value }}' } } }, + }, + { + displayName: 'PDF', + name: 'pdf', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeHTML'] } }, + options: [ + { + displayName: 'Should Parse', + name: 'shouldParse', + type: 'boolean', + default: true, + description: 'Whether true, PDF URLs are fetched and parsed. When false, PDF URLs are skipped and a 400 PDF_SKIPPED is returned.', + }, + { + displayName: 'Start', + name: 'start', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First 1-based PDF page to parse. When omitted, parsing starts at the first page.', + }, + { + displayName: 'End', + name: 'end', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last 1-based PDF page to parse. When omitted, parsing ends at the last page. Must be greater than or equal to start when both are provided.', + }, + { + displayName: 'OCR', + name: 'ocr', + type: 'boolean', + default: false, + description: 'Whether true, OCR the selected PDF pages that have no usable text layer (scans), replacing each recovered page\'s text with the OCR result while pages with a real text layer keep it. Billed at 1 credit per page OCR actually recovered, on top of the base request cost. When false, no OCR runs.', + }, + ], + routing: { request: { qs: { "pdf": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeHTML'] } }, + options: [ + { + displayName: 'Actions', + name: 'actions', + type: 'string', + default: "", + description: 'Optional browser actions executed in array order after the page loads and before content is captured. Requires a paid plan. Send a JSON array in the query parameter. Maximum: 5 actions.', + routing: { request: { qs: { "actions": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Country', + name: 'country', + type: 'options', + default: "ad", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'Ao', value: "ao" }, { name: 'Ar', value: "ar" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Bq', value: "bq" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cv', value: "cv" }, { name: 'Cw', value: "cw" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gg', value: "gg" }, { name: 'Gh', value: "gh" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'Im', value: "im" }, { name: 'In', value: "in" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Je', value: "je" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Kn', value: "kn" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Me', value: "me" }, { name: 'Mf', value: "mf" }, { name: 'Mg', value: "mg" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Si', value: "si" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'Ss', value: "ss" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sx', value: "sx" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Fetch the target page through a residential proxy in this country (ISO 3166-1 alpha-2)', + routing: { request: { qs: { "country": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectors', + type: 'string', + default: "", + description: 'CSS selectors to remove from the result. Applied after includeSelectors. Exclusion takes precedence: an element matching both is removed. Examples: "nav", "footer", ".ad-banner", "[aria-hidden=true]".', + routing: { request: { qs: { "excludeSelectors": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Headers', + name: 'headers', + type: 'json', + default: "{}", + description: 'Optional outbound HTTP headers forwarded only to the target URL, sent as deep-object query params such as headers[X-Custom]=value. When provided, caching is bypassed: the result is neither read from nor written to cache.', + routing: { request: { qs: { "headers": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Include Frames', + name: 'includeFrames', + type: 'boolean', + default: false, + description: 'Whether true, iframes are rendered inline into the returned HTML', + routing: { request: { qs: { "includeFrames": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Include Selectors', + name: 'includeSelectors', + type: 'string', + default: "", + description: 'CSS selectors. When provided, only matching subtrees (and their descendants) are kept and everything else is dropped. When omitted, the entire document is kept. Examples: "article.main", "#content", "[role=main]".', + routing: { request: { qs: { "includeSelectors": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 1 day (86400000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Settle Animations', + name: 'settleAnimations', + type: 'boolean', + default: false, + description: 'Whether true, waits briefly for CSS and transition animations to settle before extracting HTML. Defaults to false. This adds a bit of latency in exchange for more stable output on animated pages.', + routing: { request: { qs: { "settleAnimations": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Use Main Content Only', + name: 'useMainContentOnly', + type: 'boolean', + default: false, + description: 'Whether true, return only the page\'s main content in the HTML response, excluding headers, footers, sidebars, and navigation when detectable', + routing: { request: { qs: { "useMainContentOnly": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Wait for MS', + name: 'waitForMs', + type: 'string', + default: "", + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Optional browser wait time in milliseconds after initial page load. Min: 0. Max: 30000 (30 seconds).', + routing: { request: { qs: { "waitForMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'ZDR', + name: 'zdr', + type: 'options', + default: "disabled", + options: [{ name: 'Disabled', value: "disabled" }, { name: 'Enabled', value: "enabled" }], + description: 'Set to enabled to bypass shared caches and omit request and response content from retained usage logs. Requires zero data retention to be enabled for your organization (contact support@context.dev), otherwise the request fails with ZDR_NOT_ENABLED. Successful ZDR responses include X-Context-ZDR: true.', + routing: { request: { qs: { "zdr": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeMarkdown'] } }, + required: true, + default: "", + description: 'Full URL to scrape into LLM usable Markdown (must include http:// or https:// protocol)', + routing: { request: { qs: { "url": '={{ $value }}' } } }, + }, + { + displayName: 'PDF', + name: 'pdf', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeMarkdown'] } }, + options: [ + { + displayName: 'Should Parse', + name: 'shouldParse', + type: 'boolean', + default: true, + description: 'Whether true, PDF URLs are fetched and parsed. When false, PDF URLs are skipped and a 400 PDF_SKIPPED is returned.', + }, + { + displayName: 'Start', + name: 'start', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First 1-based PDF page to parse. When omitted, parsing starts at the first page.', + }, + { + displayName: 'End', + name: 'end', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last 1-based PDF page to parse. When omitted, parsing ends at the last page. Must be greater than or equal to start when both are provided.', + }, + { + displayName: 'OCR', + name: 'ocr', + type: 'boolean', + default: false, + description: 'Whether true, OCR the selected PDF pages that have no usable text layer (scans), replacing each recovered page\'s text with the OCR result while pages with a real text layer keep it. Billed at 1 credit per page OCR actually recovered, on top of the base request cost. When false, no OCR runs.', + }, + ], + routing: { request: { qs: { "pdf": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeMarkdown'] } }, + options: [ + { + displayName: 'Actions', + name: 'actions', + type: 'string', + default: "", + description: 'Optional browser actions executed in array order after the page loads and before content is captured. Requires a paid plan. Send a JSON array in the query parameter. Maximum: 5 actions.', + routing: { request: { qs: { "actions": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Country', + name: 'country', + type: 'options', + default: "ad", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'Ao', value: "ao" }, { name: 'Ar', value: "ar" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Bq', value: "bq" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cv', value: "cv" }, { name: 'Cw', value: "cw" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gg', value: "gg" }, { name: 'Gh', value: "gh" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'Im', value: "im" }, { name: 'In', value: "in" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Je', value: "je" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Kn', value: "kn" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Me', value: "me" }, { name: 'Mf', value: "mf" }, { name: 'Mg', value: "mg" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Si', value: "si" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'Ss', value: "ss" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sx', value: "sx" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Fetch the target page through a residential proxy in this country (ISO 3166-1 alpha-2)', + routing: { request: { qs: { "country": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectors', + type: 'string', + default: "", + description: 'CSS selectors to remove before conversion to Markdown. Applied after includeSelectors. Exclusion takes precedence: an element matching both is removed. Examples: "nav", "footer", ".ad-banner", "[aria-hidden=true]".', + routing: { request: { qs: { "excludeSelectors": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Headers', + name: 'headers', + type: 'json', + default: "{}", + description: 'Optional outbound HTTP headers forwarded only to the target URL, sent as deep-object query params such as headers[X-Custom]=value. When provided, caching is bypassed: the result is neither read from nor written to cache.', + routing: { request: { qs: { "headers": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Include Frames', + name: 'includeFrames', + type: 'boolean', + default: false, + description: 'Whether true, the contents of iframes are rendered to Markdown', + routing: { request: { qs: { "includeFrames": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Include HTML', + name: 'includeHTML', + type: 'boolean', + default: false, + description: 'Whether true, the response also includes an `HTML` field with the page HTML the Markdown was converted from — the same body the Scrape HTML endpoint returns for the equivalent request', + routing: { request: { qs: { "includeHTML": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Include Images', + name: 'includeImages', + type: 'boolean', + default: false, + description: 'Whether include image references in Markdown output', + routing: { request: { qs: { "includeImages": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Include Links', + name: 'includeLinks', + type: 'boolean', + default: true, + description: 'Whether preserve hyperlinks in Markdown output', + routing: { request: { qs: { "includeLinks": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Include Selectors', + name: 'includeSelectors', + type: 'string', + default: "", + description: 'CSS selectors. When provided, only matching HTML subtrees (and their descendants) are kept before conversion to Markdown. When omitted, the entire document is kept. Examples: "article.main", "#content", "[role=main]".', + routing: { request: { qs: { "includeSelectors": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 1 day (86400000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Settle Animations', + name: 'settleAnimations', + type: 'boolean', + default: false, + description: 'Whether true, waits briefly for CSS and transition animations to settle before converting to Markdown. Defaults to false. This adds a bit of latency in exchange for more stable output on animated pages.', + routing: { request: { qs: { "settleAnimations": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'shortenBase64Images', + type: 'boolean', + default: true, + description: 'Whether shorten base64-encoded image data in the Markdown output', + routing: { request: { qs: { "shortenBase64Images": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Use Main Content Only', + name: 'useMainContentOnly', + type: 'boolean', + default: false, + description: 'Whether extract only the main content of the page, excluding headers, footers, sidebars, and navigation', + routing: { request: { qs: { "useMainContentOnly": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Wait for MS', + name: 'waitForMs', + type: 'string', + default: "", + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Optional browser wait time in milliseconds after initial page load before converting the page to Markdown. Min: 0. Max: 30000 (30 seconds).', + routing: { request: { qs: { "waitForMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'ZDR', + name: 'zdr', + type: 'options', + default: "disabled", + options: [{ name: 'Disabled', value: "disabled" }, { name: 'Enabled', value: "enabled" }], + description: 'Set to enabled to bypass shared caches and omit request and response content from retained usage logs. Requires zero data retention to be enabled for your organization (contact support@context.dev), otherwise the request fails with ZDR_NOT_ENABLED. Successful ZDR responses include X-Context-ZDR: true.', + routing: { request: { qs: { "zdr": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeImages'] } }, + required: true, + default: "", + description: 'Page URL to inspect. Must include http:// or https://.', + routing: { request: { qs: { "url": '={{ $value }}' } } }, + }, + { + displayName: 'Enrichment', + name: 'enrichment', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeImages'] } }, + options: [ + { + displayName: 'Resolution', + name: 'resolution', + type: 'boolean', + default: false, + description: 'Whether measure image width and height when possible', + }, + { + displayName: 'Hosted URL', + name: 'hostedUrl', + type: 'boolean', + default: false, + description: 'Whether host materializable images on the Brand.dev CDN and return their URL and MIME type', + }, + { + displayName: 'Classification', + name: 'classification', + type: 'boolean', + default: false, + description: 'Whether classify each image by visual asset type', + }, + { + displayName: 'Max Time per MS', + name: 'maxTimePerMs', + type: 'number', + default: 30000, + typeOptions: {"minValue":1,"maxValue":60000}, + description: 'Per-image enrichment timeout in milliseconds. Default: 30000. Maximum: 60000.', + }, + ], + routing: { request: { qs: { "enrichment": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeImages'] } }, + options: [ + { + displayName: 'Actions', + name: 'actions', + type: 'string', + default: "", + description: 'Optional browser actions executed in array order after the page loads and before content is captured. Requires a paid plan. Send a JSON array in the query parameter. Maximum: 5 actions.', + routing: { request: { qs: { "actions": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Dedupe', + name: 'dedupe', + type: 'boolean', + default: false, + description: 'Whether true, visually duplicate images are removed: every image is loaded and perceptually hashed, and only the highest-resolution copy of each duplicate group is kept. Images that cannot be downloaded or hashed are kept. Default: false.', + routing: { request: { qs: { "dedupe": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Headers', + name: 'headers', + type: 'json', + default: "{}", + description: 'Optional outbound HTTP headers forwarded only to the target URL, sent as deep-object query params such as headers[X-Custom]=value. When provided, caching is bypassed: the result is neither read from nor written to cache.', + routing: { request: { qs: { "headers": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Reuse a cached result this many milliseconds old or newer. Default: 86400000 (1 day). Set to 0 to bypass cache. Maximum: 2592000000 (30 days).', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Wait for MS', + name: 'waitForMs', + type: 'string', + default: "", + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Optional browser wait time in milliseconds after initial page load before collecting images. Min: 0. Max: 30000 (30 seconds).', + routing: { request: { qs: { "waitForMs": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { show: { resource: ['webScraping'], operation: ['crawlSitemap'] } }, + required: true, + default: "", + description: 'Domain to build a sitemap for', + routing: { request: { qs: { "domain": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['crawlSitemap'] } }, + options: [ + { + displayName: 'Headers', + name: 'headers', + type: 'json', + default: "{}", + description: 'Optional outbound HTTP headers forwarded only to the target URL, sent as deep-object query params such as headers[X-Custom]=value. When provided, caching is bypassed: the result is neither read from nor written to cache.', + routing: { request: { qs: { "headers": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Max Links', + name: 'maxLinks', + type: 'number', + default: 10000, + typeOptions: {"minValue":1,"maxValue":100000}, + description: 'Maximum number of links to return from the sitemap crawl. Defaults to 10,000. Minimum is 1, maximum is 100,000.', + routing: { request: { qs: { "maxLinks": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Search', + name: 'search', + type: 'string', + default: "", + description: 'Optional search phrase. When provided, the crawled sitemap is filtered to the pages whose URLs are about that phrase, most relevant first, and the request costs 2 credits instead of 1.', + routing: { request: { qs: { "search": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Sitemap URL', + name: 'sitemapUrl', + type: 'string', + default: "", + description: 'Optional explicit sitemap URL. When provided, exactly this sitemap is crawled instead of discovering the domain\'s sitemaps.', + routing: { request: { qs: { "sitemapUrl": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'URL Regex', + name: 'urlRegex', + type: 'string', + default: "", + description: 'Optional RE2-compatible regex pattern. Only URLs matching this pattern are returned and counted against maxLinks.', + routing: { request: { qs: { "urlRegex": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'ZDR', + name: 'zdr', + type: 'options', + default: "disabled", + options: [{ name: 'Disabled', value: "disabled" }, { name: 'Enabled', value: "enabled" }], + description: 'Set to enabled to bypass shared caches and omit request and response content from retained usage logs. Requires zero data retention to be enabled for your organization (contact support@context.dev), otherwise the request fails with ZDR_NOT_ENABLED. Successful ZDR responses include X-Context-ZDR: true.', + routing: { request: { qs: { "zdr": '={{ $value || undefined }}' } } }, + }, + ], + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { show: { resource: ['webScraping'], operation: ['crawlWebsiteScrapeMarkdown'] } }, + required: true, + default: "", + description: 'The starting URL for the crawl (must include http:// or https:// protocol)', + routing: { send: { property: 'url', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['crawlWebsiteScrapeMarkdown'] } }, + options: [ + { + displayName: 'Country', + name: 'country', + type: 'options', + default: "ad", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'Ao', value: "ao" }, { name: 'Ar', value: "ar" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Bq', value: "bq" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cv', value: "cv" }, { name: 'Cw', value: "cw" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gg', value: "gg" }, { name: 'Gh', value: "gh" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'Im', value: "im" }, { name: 'In', value: "in" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Je', value: "je" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Kn', value: "kn" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Me', value: "me" }, { name: 'Mf', value: "mf" }, { name: 'Mg', value: "mg" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Si', value: "si" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'Ss', value: "ss" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sx', value: "sx" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Fetch the target page through a residential proxy in this country (ISO 3166-1 alpha-2)', + routing: { send: { property: 'country', type: 'body' } }, + }, + { + displayName: 'End', + name: 'pdfEnd', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last 1-based PDF page to parse. When omitted, parsing ends at the last page. Must be greater than or equal to start when both are provided.', + routing: { send: { property: 'pdf.end', type: 'body' } }, + }, + { + displayName: 'Exclude Selectors', + name: 'excludeSelectors', + type: 'string', + default: "", + description: 'CSS selectors to remove before each crawled page is converted to Markdown. Applied after includeSelectors. Exclusion takes precedence: an element matching both is removed. Examples: "nav", "footer", ".ad-banner", "[aria-hidden=true]".', + routing: { send: { property: 'excludeSelectors', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Follow Subdomains', + name: 'followSubdomains', + type: 'boolean', + default: false, + description: 'Whether true, follow links on subdomains of the starting URL\'s domain (e.g. docs.example.com when starting from example.com). www and apex are always treated as equivalent.', + routing: { send: { property: 'followSubdomains', type: 'body' } }, + }, + { + displayName: 'Include Frames', + name: 'includeFrames', + type: 'boolean', + default: false, + description: 'Whether true, the contents of iframes are rendered to Markdown for each crawled page', + routing: { send: { property: 'includeFrames', type: 'body' } }, + }, + { + displayName: 'Include Images', + name: 'includeImages', + type: 'boolean', + default: false, + description: 'Whether include image references in the Markdown output', + routing: { send: { property: 'includeImages', type: 'body' } }, + }, + { + displayName: 'Include Links', + name: 'includeLinks', + type: 'boolean', + default: true, + description: 'Whether preserve hyperlinks in the Markdown output', + routing: { send: { property: 'includeLinks', type: 'body' } }, + }, + { + displayName: 'Include Selectors', + name: 'includeSelectors', + type: 'string', + default: "", + description: 'CSS selectors. When provided, only matching HTML subtrees (and their descendants) are kept before each crawled page is converted to Markdown. When omitted, the entire document is kept. Examples: "article.main", "#content", "[role=main]".', + routing: { send: { property: 'includeSelectors', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'number', + default: 86400000, + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached result if a prior scrape for the same parameters exists and is younger than this many milliseconds. Defaults to 1 day (86400000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh.', + routing: { send: { property: 'maxAgeMs', type: 'body' } }, + }, + { + displayName: 'Max Depth', + name: 'maxDepth', + type: 'number', + default: 0, + typeOptions: {"minValue":0}, + description: 'Maximum link depth from the starting URL (0 = only the starting page)', + routing: { send: { property: 'maxDepth', type: 'body' } }, + }, + { + displayName: 'Max Pages', + name: 'maxPages', + type: 'number', + default: 100, + typeOptions: {"minValue":1,"maxValue":500}, + description: 'Maximum number of pages to crawl. Hard cap: 500.', + routing: { send: { property: 'maxPages', type: 'body' } }, + }, + { + displayName: 'OCR', + name: 'pdfOcr', + type: 'boolean', + default: false, + description: 'Whether true, OCR the selected PDF pages that have no usable text layer (scans), replacing each recovered page\'s text with the OCR result while pages with a real text layer keep it. Billed at 1 credit per page OCR actually recovered, on top of the base request cost.', + routing: { send: { property: 'pdf.ocr', type: 'body' } }, + }, + { + displayName: 'Settle Animations', + name: 'settleAnimations', + type: 'boolean', + default: false, + description: 'Whether true, waits briefly for CSS and transition animations to settle before extracting each crawled page. Defaults to false. This adds a bit of latency in exchange for more stable output on animated pages.', + routing: { send: { property: 'settleAnimations', type: 'body' } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'shortenBase64Images', + type: 'boolean', + default: true, + description: 'Whether truncate base64-encoded image data in the Markdown output', + routing: { send: { property: 'shortenBase64Images', type: 'body' } }, + }, + { + displayName: 'Should Parse', + name: 'pdfShouldParse', + type: 'boolean', + default: true, + description: 'Whether true, PDF pages are fetched and parsed. When false, PDF pages are skipped entirely (not included in results and not counted as failures).', + routing: { send: { property: 'pdf.shouldParse', type: 'body' } }, + }, + { + displayName: 'Start', + name: 'pdfStart', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First 1-based PDF page to parse. When omitted, parsing starts at the first page.', + routing: { send: { property: 'pdf.start', type: 'body' } }, + }, + { + displayName: 'Stop After MS', + name: 'stopAfterMs', + type: 'number', + default: 80000, + typeOptions: {"minValue":10000,"maxValue":110000}, + description: 'Soft time budget for the crawl in milliseconds. After each scrape, the crawler checks the elapsed time and, if exceeded, returns the pages collected so far instead of continuing. Min: 10000 (10s). Max: 110000 (110s). Default: 80000 (80s).', + routing: { send: { property: 'stopAfterMs', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + { + displayName: 'URL Regex', + name: 'urlRegex', + type: 'string', + default: "", + description: 'Regex pattern. Only URLs matching this pattern will be followed and scraped.', + routing: { send: { property: 'urlRegex', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Use Main Content Only', + name: 'useMainContentOnly', + type: 'boolean', + default: false, + description: 'Whether extract only the main content, stripping headers, footers, sidebars, and navigation', + routing: { send: { property: 'useMainContentOnly', type: 'body' } }, + }, + { + displayName: 'Wait for MS', + name: 'waitForMs', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Optional browser wait time in milliseconds after initial page load for each crawled page. Min: 0. Max: 30000 (30 seconds).', + routing: { send: { property: 'waitForMs', type: 'body' } }, + }, + { + displayName: 'ZDR', + name: 'zdr', + type: 'options', + default: "disabled", + options: [{ name: 'Disabled', value: "disabled" }, { name: 'Enabled', value: "enabled" }], + description: 'Set to enabled to bypass shared caches and omit request and response content from retained usage logs. Requires zero data retention to be enabled for your organization (contact support@context.dev), otherwise the request fails with ZDR_NOT_ENABLED. Successful ZDR responses include X-Context-ZDR: true.', + routing: { send: { property: 'zdr', type: 'body' } }, + }, + ], + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { show: { resource: ['webScraping'], operation: ['webSearch'] } }, + required: true, + default: "", + description: 'Search query. Accepts natural language as well as Google-style search operators such as `site:`, `-site:`, `inurl:`, `intitle:`, quoted phrases, and `OR`.', + routing: { send: { property: 'query', type: 'body' } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['webSearch'] } }, + options: [ + { + displayName: 'Country', + name: 'country', + type: 'options', + default: "af", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'An', value: "an" }, { name: 'Ao', value: "ao" }, { name: 'Aq', value: "aq" }, { name: 'Ar', value: "ar" }, { name: 'As', value: "as" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bt', value: "bt" }, { name: 'Bv', value: "bv" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cc', value: "cc" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Ck', value: "ck" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cu', value: "cu" }, { name: 'Cv', value: "cv" }, { name: 'Cx', value: "cx" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Eh', value: "eh" }, { name: 'Er', value: "er" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fk', value: "fk" }, { name: 'Fm', value: "fm" }, { name: 'Fo', value: "fo" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gh', value: "gh" }, { name: 'Gi', value: "gi" }, { name: 'Gl', value: "gl" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gs', value: "gs" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hm', value: "hm" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'In', value: "in" }, { name: 'Io', value: "io" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Ki', value: "ki" }, { name: 'Km', value: "km" }, { name: 'Kn', value: "kn" }, { name: 'Kp', value: "kp" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Li', value: "li" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Mg', value: "mg" }, { name: 'Mh', value: "mh" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mp', value: "mp" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'MS', value: "ms" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Nf', value: "nf" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nr', value: "nr" }, { name: 'Nu', value: "nu" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pm', value: "pm" }, { name: 'Pn', value: "pn" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Pw', value: "pw" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sb', value: "sb" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Sh', value: "sh" }, { name: 'Si', value: "si" }, { name: 'Sj', value: "sj" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tf', value: "tf" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tk', value: "tk" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'To', value: "to" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tv', value: "tv" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Um', value: "um" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Va', value: "va" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Vu', value: "vu" }, { name: 'Wf', value: "wf" }, { name: 'Ws', value: "ws" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Two-letter ISO 3166-1 alpha-2 country code to localize results to a specific country (maps to Google\'s `gl` parameter). Example: "us", "gb", "de".', + routing: { send: { property: 'country', type: 'body' } }, + }, + { + displayName: 'Enabled', + name: 'markdownOptionsEnabled', + type: 'boolean', + default: false, + description: 'Whether scrape each result to Markdown. Off by default to keep search cheap and fast.', + routing: { send: { property: 'markdownOptions.enabled', type: 'body' } }, + }, + { + displayName: 'End', + name: 'markdownOptionsPdfEnd', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'Last PDF page to parse (1-based, inclusive). Defaults to the final page. Must be >= start.', + routing: { send: { property: 'markdownOptions.pdf.end', type: 'body' } }, + }, + { + displayName: 'Exclude Domains', + name: 'excludeDomains', + type: 'string', + default: "", + description: 'Blocklist — drop results from these domains. Example: ["pinterest.com", "reddit.com"].', + routing: { send: { property: 'excludeDomains', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Freshness', + name: 'freshness', + type: 'options', + default: "last_24_hours", + options: [{ name: 'Last 24 Hours', value: "last_24_hours" }, { name: 'Last Month', value: "last_month" }, { name: 'Last Week', value: "last_week" }, { name: 'Last Year', value: "last_year" }], + description: 'Restrict results to content published within this window', + routing: { send: { property: 'freshness', type: 'body' } }, + }, + { + displayName: 'Include Domains', + name: 'includeDomains', + type: 'string', + default: "", + description: 'Allowlist — only return results from these domains. Example: ["arxiv.org", "github.com"].', + routing: { send: { property: 'includeDomains', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Include Frames', + name: 'markdownOptionsIncludeFrames', + type: 'boolean', + default: false, + description: 'Whether render iframe contents into the Markdown', + routing: { send: { property: 'markdownOptions.includeFrames', type: 'body' } }, + }, + { + displayName: 'Include Images', + name: 'markdownOptionsIncludeImages', + type: 'boolean', + default: false, + description: 'Whether emit image references in the Markdown', + routing: { send: { property: 'markdownOptions.includeImages', type: 'body' } }, + }, + { + displayName: 'Include Links', + name: 'markdownOptionsIncludeLinks', + type: 'boolean', + default: true, + description: 'Whether keep hyperlinks in the Markdown', + routing: { send: { property: 'markdownOptions.includeLinks', type: 'body' } }, + }, + { + displayName: 'Max Age MS', + name: 'markdownOptionsMaxAgeMs', + type: 'number', + default: 86400000, + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Cache TTL in ms for scraped Markdown keyed by URL + options. Default 1 day, max 30 days. Set to 0 to force a fresh scrape.', + routing: { send: { property: 'markdownOptions.maxAgeMs', type: 'body' } }, + }, + { + displayName: 'Num Results', + name: 'numResults', + type: 'number', + default: 10, + typeOptions: {"minValue":10,"maxValue":100}, + description: 'Number of results to request and return (10–100). Defaults to 10.', + routing: { send: { property: 'numResults', type: 'body' } }, + }, + { + displayName: 'Query Fanout', + name: 'queryFanout', + type: 'boolean', + default: false, + description: 'Whether expand the query into multiple parallel variants for broader recall', + routing: { send: { property: 'queryFanout', type: 'body' } }, + }, + { + displayName: 'Shorten Base64 Images', + name: 'markdownOptionsShortenBase64Images', + type: 'boolean', + default: true, + description: 'Whether truncate inline base64 image payloads to keep responses small', + routing: { send: { property: 'markdownOptions.shortenBase64Images', type: 'body' } }, + }, + { + displayName: 'Should Parse', + name: 'markdownOptionsPdfShouldParse', + type: 'boolean', + default: true, + description: 'Whether parse PDF URLs. When false, PDF results are skipped with WEBSITE_ACCESS_ERROR.', + routing: { send: { property: 'markdownOptions.pdf.shouldParse', type: 'body' } }, + }, + { + displayName: 'Start', + name: 'markdownOptionsPdfStart', + type: 'number', + default: 1, + typeOptions: {"minValue":1}, + description: 'First PDF page to parse (1-based, inclusive). Defaults to page 1.', + routing: { send: { property: 'markdownOptions.pdf.start', type: 'body' } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { send: { property: 'tags', type: 'body', value: '={{ $value || undefined }}' } }, + }, + { + displayName: 'Timeout MS', + name: 'markdownOptionsTimeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'markdownOptions.timeoutMS', type: 'body' } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1000, + typeOptions: {"minValue":1000,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { send: { property: 'timeoutMS', type: 'body' } }, + }, + { + displayName: 'Use Main Content Only', + name: 'markdownOptionsUseMainContentOnly', + type: 'boolean', + default: false, + description: 'Whether strip nav, header, footer, and sidebar — keep only the primary article content', + routing: { send: { property: 'markdownOptions.useMainContentOnly', type: 'body' } }, + }, + { + displayName: 'Wait for MS', + name: 'markdownOptionsWaitForMs', + type: 'number', + default: 0, + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Extra wait after page load before rendering, in ms (0–30000). Useful for JS-heavy pages.', + routing: { send: { property: 'markdownOptions.waitForMs', type: 'body' } }, + }, + ], + }, + { + displayName: 'Viewport', + name: 'viewport', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeScreenshot'] } }, + options: [ + { + displayName: 'Width', + name: 'width', + type: 'number', + default: 1920, + typeOptions: {"minValue":240,"maxValue":7680}, + description: 'Viewport width in pixels', + }, + { + displayName: 'Height', + name: 'height', + type: 'number', + default: 1080, + typeOptions: {"minValue":240,"maxValue":4320}, + description: 'Viewport height in pixels', + }, + ], + routing: { request: { qs: { "viewport": '={{ $value }}' } } }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { show: { resource: ['webScraping'], operation: ['scrapeScreenshot'] } }, + options: [ + { + displayName: 'Clear Popups', + name: 'clearPopups', + type: 'boolean', + default: false, + description: 'Whether optional parameter for comprehensive popup cleanup. If \'true\', the browser dismisses detected cookie/consent UI and clears other detected obstructive popups and overlays before capture. If \'false\' or not provided, this parameter requests no cleanup; handleCookiePopup can still request cookie/consent handling independently.', + routing: { request: { qs: { "clearPopups": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Color Scheme', + name: 'colorScheme', + type: 'options', + default: "light", + options: [{ name: 'Dark', value: "dark" }, { name: 'Light', value: "light" }], + description: 'Optional parameter to choose the site\'s visual theme in the screenshot. Use \'light\' or \'dark\' when the site offers both appearances.', + routing: { request: { qs: { "colorScheme": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Country', + name: 'country', + type: 'options', + default: "ad", + options: [{ name: 'Ad', value: "ad" }, { name: 'Ae', value: "ae" }, { name: 'Af', value: "af" }, { name: 'Ag', value: "ag" }, { name: 'AI', value: "ai" }, { name: 'Al', value: "al" }, { name: 'Am', value: "am" }, { name: 'Ao', value: "ao" }, { name: 'Ar', value: "ar" }, { name: 'At', value: "at" }, { name: 'Au', value: "au" }, { name: 'Aw', value: "aw" }, { name: 'Az', value: "az" }, { name: 'Ba', value: "ba" }, { name: 'Bb', value: "bb" }, { name: 'Bd', value: "bd" }, { name: 'Be', value: "be" }, { name: 'Bf', value: "bf" }, { name: 'Bg', value: "bg" }, { name: 'Bh', value: "bh" }, { name: 'Bi', value: "bi" }, { name: 'Bj', value: "bj" }, { name: 'Bm', value: "bm" }, { name: 'Bn', value: "bn" }, { name: 'Bo', value: "bo" }, { name: 'Bq', value: "bq" }, { name: 'Br', value: "br" }, { name: 'Bs', value: "bs" }, { name: 'Bw', value: "bw" }, { name: 'By', value: "by" }, { name: 'Bz', value: "bz" }, { name: 'Ca', value: "ca" }, { name: 'Cd', value: "cd" }, { name: 'Cf', value: "cf" }, { name: 'Cg', value: "cg" }, { name: 'Ch', value: "ch" }, { name: 'Ci', value: "ci" }, { name: 'Cl', value: "cl" }, { name: 'Cm', value: "cm" }, { name: 'Cn', value: "cn" }, { name: 'Co', value: "co" }, { name: 'Cr', value: "cr" }, { name: 'Cv', value: "cv" }, { name: 'Cw', value: "cw" }, { name: 'Cy', value: "cy" }, { name: 'Cz', value: "cz" }, { name: 'De', value: "de" }, { name: 'Dj', value: "dj" }, { name: 'Dk', value: "dk" }, { name: 'Dm', value: "dm" }, { name: 'Do', value: "do" }, { name: 'Dz', value: "dz" }, { name: 'Ec', value: "ec" }, { name: 'Ee', value: "ee" }, { name: 'Eg', value: "eg" }, { name: 'Es', value: "es" }, { name: 'Et', value: "et" }, { name: 'Fi', value: "fi" }, { name: 'Fj', value: "fj" }, { name: 'Fr', value: "fr" }, { name: 'Ga', value: "ga" }, { name: 'Gb', value: "gb" }, { name: 'Gd', value: "gd" }, { name: 'Ge', value: "ge" }, { name: 'Gf', value: "gf" }, { name: 'Gg', value: "gg" }, { name: 'Gh', value: "gh" }, { name: 'Gm', value: "gm" }, { name: 'Gn', value: "gn" }, { name: 'Gp', value: "gp" }, { name: 'Gq', value: "gq" }, { name: 'Gr', value: "gr" }, { name: 'Gt', value: "gt" }, { name: 'Gu', value: "gu" }, { name: 'Gw', value: "gw" }, { name: 'Gy', value: "gy" }, { name: 'Hk', value: "hk" }, { name: 'Hn', value: "hn" }, { name: 'Hr', value: "hr" }, { name: 'Ht', value: "ht" }, { name: 'Hu', value: "hu" }, { name: 'ID', value: "id" }, { name: 'Ie', value: "ie" }, { name: 'Il', value: "il" }, { name: 'Im', value: "im" }, { name: 'In', value: "in" }, { name: 'Iq', value: "iq" }, { name: 'Ir', value: "ir" }, { name: 'Is', value: "is" }, { name: 'It', value: "it" }, { name: 'Je', value: "je" }, { name: 'Jm', value: "jm" }, { name: 'Jo', value: "jo" }, { name: 'Jp', value: "jp" }, { name: 'Ke', value: "ke" }, { name: 'Kg', value: "kg" }, { name: 'Kh', value: "kh" }, { name: 'Kn', value: "kn" }, { name: 'Kr', value: "kr" }, { name: 'Kw', value: "kw" }, { name: 'Ky', value: "ky" }, { name: 'Kz', value: "kz" }, { name: 'La', value: "la" }, { name: 'Lb', value: "lb" }, { name: 'Lc', value: "lc" }, { name: 'Lk', value: "lk" }, { name: 'Lr', value: "lr" }, { name: 'Ls', value: "ls" }, { name: 'Lt', value: "lt" }, { name: 'Lu', value: "lu" }, { name: 'Lv', value: "lv" }, { name: 'Ly', value: "ly" }, { name: 'Ma', value: "ma" }, { name: 'Mc', value: "mc" }, { name: 'Md', value: "md" }, { name: 'Me', value: "me" }, { name: 'Mf', value: "mf" }, { name: 'Mg', value: "mg" }, { name: 'Mk', value: "mk" }, { name: 'Ml', value: "ml" }, { name: 'Mm', value: "mm" }, { name: 'Mn', value: "mn" }, { name: 'Mo', value: "mo" }, { name: 'Mq', value: "mq" }, { name: 'Mr', value: "mr" }, { name: 'Mt', value: "mt" }, { name: 'Mu', value: "mu" }, { name: 'Mv', value: "mv" }, { name: 'Mw', value: "mw" }, { name: 'Mx', value: "mx" }, { name: 'My', value: "my" }, { name: 'Mz', value: "mz" }, { name: 'Na', value: "na" }, { name: 'Nc', value: "nc" }, { name: 'Ne', value: "ne" }, { name: 'Ng', value: "ng" }, { name: 'Ni', value: "ni" }, { name: 'Nl', value: "nl" }, { name: 'No', value: "no" }, { name: 'Np', value: "np" }, { name: 'Nz', value: "nz" }, { name: 'Om', value: "om" }, { name: 'Pa', value: "pa" }, { name: 'Pe', value: "pe" }, { name: 'Pf', value: "pf" }, { name: 'Pg', value: "pg" }, { name: 'Ph', value: "ph" }, { name: 'Pk', value: "pk" }, { name: 'Pl', value: "pl" }, { name: 'Pr', value: "pr" }, { name: 'Ps', value: "ps" }, { name: 'Pt', value: "pt" }, { name: 'Py', value: "py" }, { name: 'Qa', value: "qa" }, { name: 'Re', value: "re" }, { name: 'Ro', value: "ro" }, { name: 'Rs', value: "rs" }, { name: 'Ru', value: "ru" }, { name: 'Rw', value: "rw" }, { name: 'Sa', value: "sa" }, { name: 'Sc', value: "sc" }, { name: 'Sd', value: "sd" }, { name: 'Se', value: "se" }, { name: 'Sg', value: "sg" }, { name: 'Si', value: "si" }, { name: 'Sk', value: "sk" }, { name: 'Sl', value: "sl" }, { name: 'Sm', value: "sm" }, { name: 'Sn', value: "sn" }, { name: 'So', value: "so" }, { name: 'Sr', value: "sr" }, { name: 'Ss', value: "ss" }, { name: 'St', value: "st" }, { name: 'Sv', value: "sv" }, { name: 'Sx', value: "sx" }, { name: 'Sy', value: "sy" }, { name: 'Sz', value: "sz" }, { name: 'Tc', value: "tc" }, { name: 'Td', value: "td" }, { name: 'Tg', value: "tg" }, { name: 'Th', value: "th" }, { name: 'Tj', value: "tj" }, { name: 'Tl', value: "tl" }, { name: 'Tm', value: "tm" }, { name: 'Tn', value: "tn" }, { name: 'Tr', value: "tr" }, { name: 'Tt', value: "tt" }, { name: 'Tw', value: "tw" }, { name: 'Tz', value: "tz" }, { name: 'Ua', value: "ua" }, { name: 'Ug', value: "ug" }, { name: 'Us', value: "us" }, { name: 'Uy', value: "uy" }, { name: 'Uz', value: "uz" }, { name: 'Vc', value: "vc" }, { name: 'Ve', value: "ve" }, { name: 'Vg', value: "vg" }, { name: 'Vi', value: "vi" }, { name: 'Vn', value: "vn" }, { name: 'Ye', value: "ye" }, { name: 'Yt', value: "yt" }, { name: 'Za', value: "za" }, { name: 'Zm', value: "zm" }, { name: 'Zw', value: "zw" }], + description: 'Fetch the target page through a residential proxy in this country (ISO 3166-1 alpha-2)', + routing: { request: { qs: { "country": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Direct URL', + name: 'directUrl', + type: 'string', + default: "", + description: 'A specific URL to screenshot directly, bypassing domain resolution (e.g., \'https://example.com/pricing\'). When provided, the screenshot is taken of this exact URL. You must provide either \'domain\' or \'directUrl\', but not both.', + routing: { request: { qs: { "directUrl": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: "", + description: 'Domain name to take screenshot of (e.g., \'example.com\', \'google.com\'). The domain will be automatically normalized and validated. You must provide either \'domain\' or \'directUrl\', but not both.', + routing: { request: { qs: { "domain": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Full Screenshot', + name: 'fullScreenshot', + type: 'options', + default: "true", + options: [{ name: 'False', value: "false" }, { name: 'True', value: "true" }], + description: 'Optional parameter to determine screenshot type. If \'true\', takes a full page screenshot capturing all content. If \'false\' or not provided, takes a viewport screenshot (standard browser view).', + routing: { request: { qs: { "fullScreenshot": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Handle Cookie Popup', + name: 'handleCookiePopup', + type: 'boolean', + default: false, + description: 'Whether optional parameter to control cookie/consent popup handling. If \'true\', we dismiss cookie banner before capture. If \'false\' or not provided, captures the page without that step.', + routing: { request: { qs: { "handleCookiePopup": '={{ $value ? "true" : "false" }}' } } }, + }, + { + displayName: 'Max Age MS', + name: 'maxAgeMs', + type: 'string', + default: '', + typeOptions: {"minValue":0,"maxValue":2592000000}, + description: 'Return a cached screenshot if a prior screenshot for the same parameters exists and is younger than this many milliseconds. Defaults to 1 day (86400000 ms) when omitted. Max is 30 days (2592000000 ms). Set to 0 to always capture fresh.', + routing: { request: { qs: { "maxAgeMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Page', + name: 'page', + type: 'options', + default: "login", + options: [{ name: 'Blog', value: "blog" }, { name: 'Careers', value: "careers" }, { name: 'Contact', value: "contact" }, { name: 'Login', value: "login" }, { name: 'Pricing', value: "pricing" }, { name: 'Privacy', value: "privacy" }, { name: 'Signup', value: "signup" }, { name: 'Terms', value: "terms" }], + description: 'Optional parameter to specify which page type to screenshot. If provided, the system will scrape the domain\'s links and use heuristics to find the most appropriate URL for the specified page type (30 supported languages). If not provided, screenshots the main domain landing page. Only applicable when using \'domain\', not \'directUrl\'.', + routing: { request: { qs: { "page": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Scroll Offset', + name: 'scrollOffset', + type: 'string', + default: "", + typeOptions: {"minValue":0,"maxValue":100000}, + description: 'Optional vertical scroll offset in pixels for capturing a long page in viewport-sized chunks. When provided, the full page is captured once and the returned image is the viewport-sized slice that begins at this Y offset (e.g. request scrollOffset=0, then 1080, then 2160 to walk a 1920x1080 landing page top to bottom). The final slice may be shorter than the viewport height. Takes precedence over fullScreenshot. Max: 100000.', + routing: { request: { qs: { "scrollOffset": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: "", + description: 'Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.', + routing: { request: { qs: { "tags": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Timeout MS', + name: 'timeoutMS', + type: 'number', + default: 1, + typeOptions: {"minValue":1,"maxValue":300000}, + description: 'Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).', + routing: { request: { qs: { "timeoutMS": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'Wait for MS', + name: 'waitForMs', + type: 'string', + default: '', + typeOptions: {"minValue":0,"maxValue":30000}, + description: 'Optional browser wait time in milliseconds after initial page load before taking the screenshot. Min: 0. Max: 30000 (30 seconds). Defaults to 3000 ms when omitted.', + routing: { request: { qs: { "waitForMs": '={{ $value || undefined }}' } } }, + }, + { + displayName: 'ZDR', + name: 'zdr', + type: 'options', + default: "disabled", + options: [{ name: 'Disabled', value: "disabled" }, { name: 'Enabled', value: "enabled" }], + description: 'Set to enabled to bypass shared caches and omit request and response content from retained usage logs. Requires zero data retention to be enabled for your organization (contact support@context.dev), otherwise the request fails with ZDR_NOT_ENABLED. Successful ZDR responses include X-Context-ZDR: true.', + routing: { request: { qs: { "zdr": '={{ $value || undefined }}' } } }, + }, + ], + }, +]; From 6452d3e6e95a0fddd1a7811eb54743f9459bd44b Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Thu, 20 Aug 2026 04:45:14 +0530 Subject: [PATCH 08/10] Add live conformance suite runnable against any API key Exercises the node's operations against the real API and prints a pass/fail line per operation. Requests are assembled from the built node's own routing in dist/, so a green run is evidence about the artifact being published rather than a parallel client. Read-only operations run by default (33/33 passing). Cache-warming and mutating operations are opt-in. Lifecycle fixtures create a record, run every dependent operation against it, and delete it in a finally block, covering the operations that need an existing id. Every request carries a configurable tag so suite traffic stays separable from real usage. Rate limits are retried with backoff so throttling cannot strand a cleanup. CONTEXT_DEV_API_KEY=... npm run test:live Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018eb9URz1xqidX41rxa4GPZ --- tests/live/README.md | 53 +++ tests/live/fixtures.json | 839 +++++++++++++++++++++++++++++++++++++++ tests/live/run-live.mjs | 390 ++++++++++++++++++ 3 files changed, 1282 insertions(+) create mode 100644 tests/live/README.md create mode 100644 tests/live/fixtures.json create mode 100644 tests/live/run-live.mjs diff --git a/tests/live/README.md b/tests/live/README.md new file mode 100644 index 0000000..1fc16cc --- /dev/null +++ b/tests/live/README.md @@ -0,0 +1,53 @@ +# Live conformance suite + +Exercises every operation this node exposes against the real API and prints a pass/fail line per +operation. Requests are assembled from the **built node's own routing** (`dist/`) rather than a +separate client, so a green run is evidence about the artifact you are about to publish. + +## Run it + +```sh +npm run build +CONTEXT_DEV_API_KEY=sk-... node tests/live/run-live.mjs +``` + +| Flag | Effect | +|---|---| +| `--only ` | run just the operations matching a resource, operation or label | +| `--include-cache-warm` | also run cache-warming operations (these consume credits) | +| `--include-mutating` | also run operations that create or change data | +| `--json ` | write a machine-readable report | +| `--verbose` | print each request, response excerpt, and the full skip list | + +Exit code is non-zero if any operation fails, so it drops straight into CI. + +## What runs by default + +Only read-only operations with complete arguments — 33 of 53. Everything else is +listed under **Skipped** with the reason: + +- **needs values** — a required parameter has no sample value in `fixtures.json` +- **needs an existing record id** — the path contains an id (`/monitors/{monitor_id}`) +- **mutating** / **cache-warm** — opt in with the flags above +- **binary upload** — supply a file to exercise it + +## Adding coverage + +`fixtures.json` is a plain list. To enable an operation, fill in its `args` and remove the +`needsValues` marker. To test an id-bound operation, paste a real id into `args` and change +`risk` to `read`. Keep expectations semantic — status codes, not response snapshots — so the suite +does not go red every time the API adds a field. + +A deliberate plan-gated status (a `403` for a feature your key does not include) belongs in +`expect.statuses` so it reads as an allowed result rather than a failure. + +## Lifecycles + +`lifecycles` in `fixtures.json` cover operations bound to a record id, which cannot be tested +against an empty account. Each one creates a record, lets every dependent operation run against the +captured id, then deletes it in a `finally` block so a mid-run failure still cleans up. They only +run with `--include-mutating`. + +A lifecycle without a `delete` leaves its record behind on every run — the runner prints a warning +naming what remains. Rate limiting is retried with backoff (`--retries`, `--delay`) specifically so +a throttled cleanup does not strand records. diff --git a/tests/live/fixtures.json b/tests/live/fixtures.json new file mode 100644 index 0000000..d347c70 --- /dev/null +++ b/tests/live/fixtures.json @@ -0,0 +1,839 @@ +{ + "$comment": "Live-suite fixtures. `args` are keyed by the parameter names the node sends. Fill in `needsValues` entries and delete the marker to enable an operation; supply real ids to enable the needs-id ones.", + "apiKeyEnv": "CONTEXT_DEV_API_KEY", + "tags": [ + "n8n-live-test" + ], + "lifecycles": [ + { + "name": "monitor", + "create": "monitors.createMonitor", + "captures": [ + "monitor_id" + ], + "delete": "monitors.deleteMonitor" + }, + { + "name": "batch", + "create": "batch.submitBatch", + "captures": [ + "batch_id" + ], + "_note": "no delete: the documented DELETE /batch/{batch_id} is not implemented server-side; cancelled batches are terminal and remain listed" + } + ], + "fixtures": [ + { + "resource": "news", + "operation": "searchCompanyNews", + "label": "Search company news", + "risk": "read", + "args": { + "searchBy.entity.name": "Context.dev", + "searchBy.entity.domain": "context.dev", + "searchBy.entity.ticker": "AAPL", + "searchBy.entity.isin": "US0378331005" + }, + "needsValues": [ + "searchBy.type", + "searchBy.entity.type", + "sortBy.type" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "parsing", + "operation": "parseBytes", + "label": "Parse Bytes", + "risk": "read", + "args": {}, + "needsBinary": true, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "scrapeHTML", + "label": "Scrape HTML", + "risk": "read", + "args": { + "url": "https://context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "scrapeMarkdown", + "label": "Scrape Markdown", + "risk": "read", + "args": { + "url": "https://context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "scrapeImages", + "label": "Scrape Images", + "risk": "read", + "args": { + "url": "https://context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "crawlSitemap", + "label": "Crawl Sitemap", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "crawlWebsiteScrapeMarkdown", + "label": "Crawl Website & Scrape Markdown", + "risk": "read", + "args": { + "url": "https://context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "webSearch", + "label": "Web Search", + "risk": "read", + "args": { + "query": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webScraping", + "operation": "scrapeScreenshot", + "label": "Scrape Screenshot", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "extractStructuredWebsiteData", + "label": "Extract Structured Website Data", + "risk": "read", + "args": { + "url": "https://context.dev", + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + } + } + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "findWebsiteCompetitors", + "label": "Find website competitors", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "scrapeStyleguide", + "label": "Scrape Styleguide", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "scrapeFonts", + "label": "Scrape Fonts", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "classifyNAICSIndustries", + "label": "Classify NAICS industries", + "risk": "read", + "args": { + "input": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "classifySICIndustries", + "label": "Classify SIC industries", + "risk": "read", + "args": { + "input": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "queryWebsiteDataUsingAI", + "label": "Query website data using AI", + "risk": "read", + "args": { + "domain": "context.dev", + "data_to_extract": [ + { + "datapoint_name": "pricing", + "datapoint_type": "text", + "datapoint_description": "Headline pricing information", + "datapoint_example": "$99 per month" + } + ] + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "extractASingleProductFromAURL", + "label": "Extract a single product from a URL", + "risk": "read", + "args": { + "url": "https://context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "webExtraction", + "operation": "extractProductsFromABrandSWebsite", + "label": "Extract products from a brand's website", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "people", + "operation": "enrichPerson", + "label": "Enrich Person", + "risk": "read", + "args": { + "name.first": "Bill", + "name.last": "Gates", + "company.domain": "microsoft.com" + }, + "expect": { + "statuses": [ + 200, + 403 + ] + } + }, + { + "resource": "people", + "operation": "retrievePerson", + "label": "Retrieve Person", + "risk": "read", + "args": { + "identifiers.linkedinUrl": "https://www.linkedin.com/in/williamhgates/" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveBrandDataByDomain", + "label": "Retrieve brand data by domain", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveBrandData", + "label": "Retrieve brand data", + "risk": "read", + "args": { + "type": "by_domain", + "domain": "stripe.com" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveBrandDataByCompanyName", + "label": "Retrieve brand data by company name", + "risk": "read", + "args": { + "name": "Context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "brandSearch", + "label": "Brand Search", + "risk": "read", + "args": { + "query": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveBrandDataByEmailAddress", + "label": "Retrieve brand data by email address", + "risk": "read", + "args": { + "email": "hello@context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveBrandDataByStockTicker", + "label": "Retrieve brand data by stock ticker", + "risk": "read", + "args": { + "ticker": "AAPL" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveBrandDataByISIN", + "label": "Retrieve brand data by ISIN", + "risk": "read", + "args": { + "isin": "US0378331005" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "identifyBrandFromTransactionData", + "label": "Identify brand from transaction data", + "risk": "read", + "args": { + "transaction_info": "AMZN Mktp US*2M35W1TR2" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "brandIntelligence", + "operation": "retrieveSimplifiedBrandDataByDomain", + "label": "Retrieve simplified brand data by domain", + "risk": "read", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "utility", + "operation": "prefetchBrandDataForADomain", + "label": "Prefetch brand data for a domain", + "risk": "cache-warm", + "args": { + "domain": "context.dev" + }, + "expect": { + "statuses": [ + 200, + 202, + 403 + ] + } + }, + { + "resource": "utility", + "operation": "prefetchBrandDataByEmail", + "label": "Prefetch brand data by email", + "risk": "cache-warm", + "args": { + "email": "hello@context.dev" + }, + "expect": { + "statuses": [ + 200, + 202, + 403 + ] + } + }, + { + "resource": "utility", + "operation": "prefetchData", + "label": "Prefetch data", + "risk": "cache-warm", + "args": { + "type": "brand", + "identifier.domain": "context.dev" + }, + "expect": { + "statuses": [ + 200, + 202, + 403 + ] + } + }, + { + "resource": "monitors", + "operation": "createMonitor", + "label": "Create a monitor", + "risk": "mutating", + "args": { + "mode": "web", + "name": "Acme pricing page", + "target": { + "type": "page", + "url": "https://acme.com/pricing" + }, + "change_detection.type": "exact", + "schedule.type": "interval", + "schedule.frequency": 6, + "schedule.unit": "hours", + "webhook.url": "https://example.com/webhook" + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "listMonitors", + "label": "List monitors", + "risk": "read", + "args": {}, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "getMonitor", + "label": "Get a monitor", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "updateMonitor", + "label": "Update a monitor", + "risk": "needs-id", + "args": { + "name": "Acme pricing monitor", + "status": "active", + "schedule.type": "interval", + "schedule.frequency": 1, + "schedule.unit": "hours", + "webhook.url": "https://example.com/webhook" + }, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "deleteMonitor", + "label": "Delete a monitor", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "listMonitorRuns", + "label": "List monitor runs", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "getMonitorRun", + "label": "Retrieve a monitor run", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id", + "run_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "listMonitorChanges", + "label": "List changes for a monitor", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "listAccountRuns", + "label": "List runs", + "risk": "read", + "args": {}, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "listMonitorCreditUsage", + "label": "Monitor credit usage", + "risk": "read", + "args": {}, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "getMonitorLimits", + "label": "Monitor limits", + "risk": "read", + "args": {}, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "listChanges", + "label": "List changes", + "risk": "read", + "args": {}, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "getChange", + "label": "Get a change", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "change_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "runMonitorNow", + "label": "Run a monitor now", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "monitors", + "operation": "rotateMonitorWebhookSecret", + "label": "Rotate a monitor webhook secret", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "monitor_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "batch", + "operation": "submitBatch", + "label": "Submit a batch", + "risk": "mutating", + "args": { + "input": { + "mode": "scrape", + "data": { + "format": "markdown", + "urls": [ + { + "url": "https://context.dev", + "itemId": "n8n-live-test-1" + } + ] + } + } + }, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "batch", + "operation": "listBatches", + "label": "List batches", + "risk": "read", + "args": {}, + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "batch", + "operation": "getBatch", + "label": "Get a batch", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "batch_id" + ], + "expect": { + "statuses": [ + 200 + ] + } + }, + { + "resource": "batch", + "operation": "deleteBatch", + "label": "Delete a batch", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "batch_id" + ], + "expect": { + "statuses": [ + 200, + 202, + 204, + 403 + ] + } + }, + { + "resource": "batch", + "operation": "getBatchResults", + "label": "Get batch results", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "batch_id" + ], + "expect": { + "statuses": [ + 200, + 409 + ] + } + }, + { + "resource": "batch", + "operation": "cancelBatch", + "label": "Cancel a batch", + "risk": "needs-id", + "args": {}, + "needsValues": [ + "batch_id" + ], + "expect": { + "statuses": [ + 200, + 202, + 409 + ] + } + } + ] +} diff --git a/tests/live/run-live.mjs b/tests/live/run-live.mjs new file mode 100644 index 0000000..9a8d54b --- /dev/null +++ b/tests/live/run-live.mjs @@ -0,0 +1,390 @@ +#!/usr/bin/env node +/** + * Live conformance suite for this n8n node. + * + * Requests are assembled from the BUILT NODE'S OWN ROUTING (dist/) rather than a hand-written + * client, so a green run is evidence about the node you are about to publish. + * + * npm run build + * CONTEXT_DEV_API_KEY=sk-... node tests/live/run-live.mjs + * + * Flags: + * --only run operations whose resource/operation/label matches + * --include-cache-warm also run cache-warming operations (they consume credits) + * --include-mutating also run operations that create or change data + * --json write a machine-readable report + * --verbose print each request and a response excerpt + */ +import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { pathToFileURL } from 'node:url'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const argv = process.argv.slice(2); +const arg = (k, d) => { const i = argv.indexOf(k); return i >= 0 ? argv[i + 1] : d; }; +const has = (k) => argv.includes(k); + +const API_KEY = process.env.CONTEXT_DEV_API_KEY || arg('--key', ''); +if (!API_KEY) { + console.error('Missing API key. Set CONTEXT_DEV_API_KEY or pass --key.'); + process.exit(2); +} + +const NODE_DIST = join(ROOT, "dist/nodes/ContextDev/ContextDev.node.js"); +if (!existsSync(NODE_DIST)) { + console.error('Built node not found at ' + NODE_DIST + '\nRun: npm run build'); + process.exit(2); +} + +// ── load the node exactly as n8n would ───────────────────────────────────────────────────────── +const mod = await import(pathToFileURL(NODE_DIST).href); +const NodeClass = mod["ContextDev"] || Object.values(mod).find((v) => typeof v === 'function'); +if (typeof NodeClass !== 'function') { + console.error('Could not find the node class in ' + NODE_DIST); + process.exit(2); +} +const description = new NodeClass().description; +const properties = description.properties; +const baseURL = description.requestDefaults?.baseURL; +const staticHeaders = { ...(description.requestDefaults?.headers || {}) }; + +// ── read the node's routing ──────────────────────────────────────────────────────────────────── +const showOf = (f) => f.displayOptions?.show || {}; +const opsOf = (f, ns) => { const a = showOf(f)['/operation'] || showOf(f).operation; return a ? a.map((v) => ns + '::' + v) : null; }; + +// operation catalogue: resource::operation → { method, url } +const catalogue = new Map(); +for (const f of properties) { + if (f.name !== 'operation') continue; + const res = (showOf(f).resource || [])[0]; + for (const o of f.options || []) { + const r = o.routing?.request; + if (r?.url) catalogue.set(res + '::' + o.value, { method: r.method, url: r.url, label: o.name, headers: r.headers || {} }); + } +} + +// field routing: which key a field sends, and where +function routedKey(field) { + const r = field.routing?.request; + if (r?.qs) return { where: 'qs', key: Object.keys(r.qs)[0] }; + if (r?.body && typeof r.body === 'object') return { where: 'body', key: Object.keys(r.body)[0] }; + if (field.routing?.send?.property) return { where: 'body', key: field.routing.send.property }; + if (Array.isArray(field.routing?.send?.preSend)) return { where: 'binary', key: '' }; + return null; +} + +// resource::operation → [{ where, key, fieldName }] +const wiring = new Map(); +function record(ops, entry) { for (const op of ops) { if (!wiring.has(op)) wiring.set(op, []); wiring.get(op).push(entry); } } +for (const f of properties) { + if (f.name === 'operation' || f.name === 'resource') continue; + const res = (showOf(f).resource || [])[0]; + if (!res) continue; + const scope = opsOf(f, res) || [...catalogue.keys()].filter((k) => k.startsWith(res + '::')); + const isColl = f.type === 'collection' || f.type === 'fixedCollection'; + const routed = routedKey(f); + if (isColl && !routed) { + for (const sub of f.options || []) { + const subScope = opsOf(sub, res) || scope; + const subRouted = routedKey(sub); + if (subRouted) record(subScope, { ...subRouted, fieldName: sub.name }); + } + continue; + } + if (routed) record(scope, { ...routed, fieldName: f.name }); +} + +// ── fixtures ─────────────────────────────────────────────────────────────────────────────────── +const doc = JSON.parse(readFileSync(join(HERE, 'fixtures.json'), 'utf8')); +const fixtures = doc.fixtures; +const TAGS = (arg('--tag') ? [arg('--tag')] : doc.tags || []); +const only = arg('--only'); +const selected = []; +const skipped = []; +const lifecycleMembers = new Set(); +for (const lc of (doc.lifecycles || [])) { + for (const k of [lc.create, lc.delete]) if (k) lifecycleMembers.add(k.includes('::') ? k : k.replace('.', '::')); +} +for (const fx of fixtures) { + const key = fx.resource + '::' + fx.operation; + const why = + lifecycleMembers.has(key) ? 'runs in the ' + 'lifecycle phase' : + !catalogue.has(key) ? 'not in the built node' + : fx.needsBinary ? 'binary upload — supply a file to test' + : fx.needsValues ? 'needs values: ' + fx.needsValues.join(', ') + : fx.risk === 'needs-id' ? 'needs an existing record id' + : fx.risk === 'mutating' && !has('--include-mutating') ? 'mutating (--include-mutating)' + : fx.risk === 'cache-warm' && !has('--include-cache-warm') ? 'cache-warm (--include-cache-warm)' + : only && !(fx.resource + ' ' + fx.operation + ' ' + (fx.label || '')).toLowerCase().includes(only.toLowerCase()) ? 'filtered out' + : null; + if (why) skipped.push({ ...fx, why }); else selected.push(fx); +} + +// ── request assembly (mirrors n8n's declarative routing) ─────────────────────────────────────── +function setDeep(obj, dotted, value) { + const parts = dotted.split('.'); + let cur = obj; + for (const p of parts.slice(0, -1)) cur = cur[p] ??= {}; + cur[parts.at(-1)] = value; +} +function buildRequest(fx) { + const entry = catalogue.get(fx.resource + '::' + fx.operation); + let path = entry.url; + const missingPathParams = []; + if (path.startsWith('=')) { + // n8n URL expression: /monitors/{{ $parameter["monitorId"] }}. The field name is camelCased + // while API ids are snake_case, so try both spellings before giving up. + const snake = (n) => n.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase(); + path = path.slice(1).replace(/\{\{\s*\$parameter\["([^"]+)"\]\s*\}\}/g, (_, n) => { + const v = fx.args[n] ?? fx.args[snake(n)]; + // An unresolved path param silently truncates the URL — /monitors/{id} becomes /monitors/, + // which hits the LIST endpoint and returns a misleading 200. Refuse to send instead. + if (v === undefined || v === null || v === '') { missingPathParams.push(n); return ''; } + return encodeURIComponent(v); + }); + } + if (missingPathParams.length) return { unresolved: missingPathParams }; + const qs = new URLSearchParams(); + const body = {}; + let hasBody = false; + for (const w of wiring.get(fx.resource + '::' + fx.operation) || []) { + if (!(w.key in fx.args)) continue; + const v = fx.args[w.key]; + if (w.where === 'qs') qs.set(w.key, typeof v === 'boolean' ? String(v) : String(v)); + else if (w.where === 'body') { setDeep(body, w.key, v); hasBody = true; } + } + // any fixture arg not wired to a field still belongs on the wire — dotted keys go to the body. + // Path params are the exception: they were already consumed by the URL, and echoing them into + // the body makes the API reject the request with "unrecognized key". + const camel = (n) => n.replace(/_(.)/g, (_, c) => c.toUpperCase()); + const pathNames = new Set(); + for (const m of String(entry.url).matchAll(/\$parameter\["([^"]+)"\]/g)) { pathNames.add(m[1]); pathNames.add(m[1].replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()); } + for (const [k, v] of Object.entries(fx.args)) { + const known = (wiring.get(fx.resource + '::' + fx.operation) || []).some((w) => w.key === k); + if (known) continue; + if (pathNames.has(k) || pathNames.has(camel(k))) continue; + if (entry.method === 'GET' || entry.method === 'DELETE') qs.set(k, String(v)); + else { setDeep(body, k, v); hasBody = true; } + } + // Tag every request the API accepts tags on, so this suite's traffic is separable from real + // usage in the vendor's dashboard. Only applied where the node actually wires a tags field. + if (TAGS.length) { + const tagWire = (wiring.get(fx.resource + '::' + fx.operation) || []).find((w) => w.key === 'tags'); + if (tagWire && !('tags' in fx.args)) { + if (tagWire.where === 'qs') qs.set('tags', TAGS.join(',')); + else setDeep(body, 'tags', TAGS), (hasBody = true); + } + } + const q = qs.toString(); + return { + method: entry.method, + url: baseURL + path + (q ? '?' + q : ''), + headers: { ...staticHeaders, ...entry.headers, Authorization: 'Bearer ' + API_KEY }, + body: hasBody ? JSON.stringify(body) : undefined, + }; +} + +// ── sending ──────────────────────────────────────────────────────────────────────────────────── +// A 429 says nothing about the node — it says the account's rate limit was reached. Retrying with +// backoff keeps rate limiting from masquerading as a conformance failure, and (critically) keeps a +// throttled cleanup from stranding records on the account. +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +const DELAY = Number(arg('--delay', '0')); +const RETRIES = Number(arg('--retries', '3')); +async function send(req) { + let res, text = ''; + for (let attempt = 0; ; attempt++) { + res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body }); + text = await res.text(); + if (res.status !== 429 || attempt >= RETRIES) break; + const retryAfter = Number(res.headers.get('retry-after')); + await sleep(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 2000 * (attempt + 1)); + } + if (DELAY) await sleep(DELAY); + return { status: res.status, text }; +} + +// ── run ──────────────────────────────────────────────────────────────────────────────────────── +const C = process.stdout.isTTY ? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', d: '\x1b[2m', b: '\x1b[1m', x: '\x1b[0m' } + : { g: '', r: '', y: '', d: '', b: '', x: '' }; +const results = []; +let lastResource = null; + +console.log(''); +console.log(C.b + description.displayName + ' — live conformance' + C.x); +console.log(C.d + baseURL + ' · ' + selected.length + ' to run, ' + skipped.length + ' skipped' + (TAGS.length ? ' · tagged ' + TAGS.join(',') : '') + C.x); +console.log(''); + +for (const fx of selected) { + if (fx.resource !== lastResource) { console.log(C.b + fx.resource + C.x); lastResource = fx.resource; } + const req = buildRequest(fx); + const started = Date.now(); + let status = 0, ok = false, note = '', bodyText = ''; + if (req.unresolved) { + results.push({ ...fx, status: 0, ok: false, ms: 0, note: 'unresolved path param: ' + req.unresolved.join(', ') }); + console.log(' ' + C.r + '✗' + C.x + ' ' + (fx.label || fx.operation).slice(0, 44).padEnd(44) + C.r + ' unresolved path param: ' + req.unresolved.join(', ') + C.x); + continue; + } + try { + const sent = await send(req); + status = sent.status; + bodyText = sent.text; + ok = fx.expect.statuses.includes(status); + if (!ok) { + // an API's error payload may nest objects or arrays (field-level validation); stringify + // rather than letting it render as [object Object] + try { + const j = JSON.parse(bodyText); + const raw = j.message ?? j.error ?? j.detail ?? j; + note = (typeof raw === 'string' ? raw : JSON.stringify(raw)).slice(0, 160); + } catch { note = bodyText.slice(0, 160).replace(/\s+/g, ' '); } + } + } catch (e) { note = e.message.slice(0, 80); } + const ms = Date.now() - started; + results.push({ ...fx, status, ok, ms, note }); + const mark = ok ? C.g + '✓' + C.x : C.r + '✗' + C.x; + const label = (fx.label || fx.operation).slice(0, 44).padEnd(44); + console.log(' ' + mark + ' ' + label + C.d + String(status).padEnd(5) + String(ms).padStart(5) + 'ms' + C.x + (note ? ' ' + C.r + note + C.x : '')); + if (has('--verbose')) { + console.log(' ' + C.d + req.method + ' ' + req.url.replace(API_KEY, '***') + C.x); + if (req.body) console.log(' ' + C.d + req.body.slice(0, 160) + C.x); + if (bodyText) console.log(' ' + C.d + bodyText.slice(0, 200).replace(/\s+/g, ' ') + C.x); + } +} + +// ── lifecycles ───────────────────────────────────────────────────────────────────────────────── +// Operations bound to a record id cannot be tested against an empty account: there is nothing to +// reference. A lifecycle creates one record, lets every dependent operation run against it, then +// deletes it in a finally block so a mid-run failure still cleans up. +const lifecycles = doc.lifecycles || []; +if (has('--include-mutating') && lifecycles.length) { + const byKey = new Map(fixtures.map((f) => [f.resource + '::' + f.operation, f])); + const norm = (k) => (k.includes('::') ? k : k.replace('.', '::')); + const findId = (obj, want) => { + // prefer the configured path, else the first id-ish key anywhere in the payload + const seen = [obj]; + while (seen.length) { + const cur = seen.shift(); + if (!cur || typeof cur !== 'object') continue; + for (const [k, v] of Object.entries(cur)) { + if (typeof v === 'string' && (k === want || k === 'id' || /(^|_)id$/.test(k))) return v; + if (v && typeof v === 'object') seen.push(v); + } + } + return null; + }; + + for (const lc of lifecycles) { + const createFx = byKey.get(norm(lc.create)); + if (!createFx) { console.log('\n' + C.y + 'lifecycle ' + lc.name + ': create fixture ' + lc.create + ' not found' + C.x); continue; } + console.log(''); + console.log(C.b + 'lifecycle: ' + lc.name + C.x); + + const captured = {}; + let createdId = null; + try { + const req = buildRequest(createFx); + const started = Date.now(); + const sent = await send(req); + const text = sent.text; + const ms = Date.now() - started; + const ok = [200, 201, 202].includes(sent.status); + let note = ''; + if (!ok) { try { const j = JSON.parse(text); const raw = j.message ?? j.error ?? j; note = (typeof raw === 'string' ? raw : JSON.stringify(raw)).slice(0, 160); } catch { note = text.slice(0, 160); } } + results.push({ ...createFx, status: sent.status, ok, ms, note }); + console.log(' ' + (ok ? C.g + '✓' + C.x : C.r + '✗' + C.x) + ' ' + (createFx.label || lc.create).slice(0, 44).padEnd(44) + C.d + String(sent.status).padEnd(5) + String(ms).padStart(5) + 'ms' + C.x + (note ? ' ' + C.r + note + C.x : '')); + if (!ok) continue; + + const parsed = JSON.parse(text); + for (const idName of lc.captures || []) { + const v = findId(parsed, idName); + if (v) { captured[idName] = v; createdId = createdId || v; } + } + if (!Object.keys(captured).length) { console.log(' ' + C.y + 'no id found in the create response — dependants stay skipped' + C.x); continue; } + console.log(' ' + C.d + Object.entries(captured).map(([k, v]) => k + '=' + v).join(' ') + C.x); + + // every fixture whose missing values are now all satisfied becomes runnable + for (const fx of fixtures) { + if (fx === createFx || (lc.delete && fx.resource + '::' + fx.operation === norm(lc.delete))) continue; + const needs = fx.needsValues || []; + if (!needs.length || !needs.every((n) => n in captured)) continue; + if (fx.risk === 'mutating' && !has('--include-mutating')) continue; + const resolved = { ...fx, args: { ...fx.args, ...captured } }; + const r2 = buildRequest(resolved); + if (r2.unresolved) { + results.push({ ...fx, status: 0, ok: false, ms: 0, note: 'unresolved path param: ' + r2.unresolved.join(', ') }); + console.log(' ' + C.r + '✗' + C.x + ' ' + (fx.label || fx.operation).slice(0, 42).padEnd(42) + C.r + ' unresolved: ' + r2.unresolved.join(', ') + C.x); + continue; + } + const t0 = Date.now(); + let st = 0, ok2 = false, note2 = ''; + try { + const sent2 = await send(r2); + st = sent2.status; + const tt = sent2.text; + ok2 = fx.expect.statuses.includes(st) || [200, 201, 202, 204].includes(st); + if (!ok2) { try { const j = JSON.parse(tt); const raw = j.message ?? j.error ?? j; note2 = (typeof raw === 'string' ? raw : JSON.stringify(raw)).slice(0, 160); } catch { note2 = tt.slice(0, 160); } } + } catch (e) { note2 = e.message.slice(0, 120); } + const ms2 = Date.now() - t0; + results.push({ ...fx, status: st, ok: ok2, ms: ms2, note: note2 }); + const idx = skipped.findIndex((sk) => sk.resource === fx.resource && sk.operation === fx.operation); + if (idx >= 0) skipped.splice(idx, 1); + console.log(' ' + (ok2 ? C.g + '✓' + C.x : C.r + '✗' + C.x) + ' ' + (fx.label || fx.operation).slice(0, 42).padEnd(42) + C.d + String(st).padEnd(5) + String(ms2).padStart(5) + 'ms' + C.x + (note2 ? ' ' + C.r + note2 + C.x : '')); + } + } finally { + // a lifecycle with no delete leaves a record behind every run — say so, loudly, rather than + // letting the account quietly fill up + if (!lc.delete && Object.keys(captured).length) { + console.log(' ' + C.y + 'no cleanup for ' + lc.name + ' — ' + Object.entries(captured).map(([k, v]) => k + '=' + v).join(' ') + ' remains on the account' + C.x); + if (lc._note) console.log(' ' + C.d + lc._note + C.x); + } + // cleanup runs even if a dependant threw — never leave records behind + if (lc.delete && Object.keys(captured).length) { + const delFx = byKey.get(norm(lc.delete)); + if (delFx) { + const resolved = { ...delFx, args: { ...delFx.args, ...captured } }; + try { + const rd = buildRequest(resolved); + const rr = await send(rd); + const okd = [200, 202, 204].includes(rr.status); + results.push({ ...delFx, status: rr.status, ok: okd, ms: 0, note: okd ? '' : 'cleanup failed' }); + console.log(' ' + (okd ? C.g + '✓' + C.x : C.r + '✗' + C.x) + ' ' + ('cleanup: ' + (delFx.label || lc.delete)).slice(0, 44).padEnd(44) + C.d + String(rr.status) + C.x); + } catch (e) { + console.log(' ' + C.r + '✗ cleanup threw: ' + e.message.slice(0, 80) + C.x); + console.log(' ' + C.r + 'LEFTOVER ' + lc.name + ' ' + JSON.stringify(captured) + C.x); + } + } + } + } + } +} + +// ── report ───────────────────────────────────────────────────────────────────────────────────── +const passed = results.filter((r) => r.ok).length; +const failed = results.length - passed; +console.log(''); +console.log(C.b + 'Result' + C.x + ' ' + C.g + passed + ' passed' + C.x + (failed ? ' ' + C.r + failed + ' failed' + C.x : '') + C.d + ' ' + skipped.length + ' skipped' + C.x); + +if (skipped.length) { + console.log(''); + console.log(C.d + 'Skipped:' + C.x); + const byReason = new Map(); + for (const s of skipped) { if (!byReason.has(s.why)) byReason.set(s.why, []); byReason.get(s.why).push(s.label || s.operation); } + for (const [why, ops] of byReason) console.log(' ' + C.d + why + ' — ' + ops.length + C.x + (has('--verbose') ? '\n ' + ops.join(', ') : '')); +} + +const jsonOut = arg('--json'); +if (jsonOut) { + writeFileSync(jsonOut, JSON.stringify({ + node: description.displayName, baseURL, ranAt: new Date().toISOString(), + passed, failed, skipped: skipped.length, + results: results.map(({ resource, operation, label, status, ok, ms, note }) => ({ resource, operation, label, status, ok, ms, note })), + skippedDetail: skipped.map(({ resource, operation, label, why }) => ({ resource, operation, label, why })), + }, null, 2) + '\n'); + console.log(''); + console.log(C.d + 'report → ' + jsonOut + C.x); +} +console.log(''); +process.exit(failed ? 1 : 0); From 4902a56819cc691f3b4d99790670a7ad35933c20 Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Thu, 20 Aug 2026 04:45:14 +0530 Subject: [PATCH 09/10] Replace structural unit tests with lint, build and live verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 246 assertions restated the API by hand and had already drifted from it — the brand routing table predated POST /brand/retrieve. They also duplicated checks the linter and compiler already make. `npm test` now runs lint and build, which is what n8n Cloud verification requires. Behaviour is covered by the live suite against the real API. CI runs the live suite when an API key is available, and skips it on forks. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018eb9URz1xqidX41rxa4GPZ --- .github/workflows/ci.yml | 7 + package.json | 19 +- tests/aiDataExtraction.test.ts | 295 -------------- tests/brand.test.ts | 318 --------------- tests/credentials.test.ts | 89 ---- tests/helpers.ts | 152 ------- tests/industry.test.ts | 147 ------- tests/monitors.test.ts | 390 ------------------ tests/node.test.ts | 281 ------------- tests/utility.test.ts | 150 ------- tests/web.test.ts | 721 --------------------------------- 11 files changed, 11 insertions(+), 2558 deletions(-) delete mode 100644 tests/aiDataExtraction.test.ts delete mode 100644 tests/brand.test.ts delete mode 100644 tests/credentials.test.ts delete mode 100644 tests/helpers.ts delete mode 100644 tests/industry.test.ts delete mode 100644 tests/monitors.test.ts delete mode 100644 tests/node.test.ts delete mode 100644 tests/utility.test.ts delete mode 100644 tests/web.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cab9aa..f37e468 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,3 +26,10 @@ jobs: - name: Run build run: 'npm run build' + + # Live conformance runs only where an API key is available (skipped on forks). + - name: Live conformance + if: ${{ env.CONTEXT_DEV_API_KEY != '' }} + env: + CONTEXT_DEV_API_KEY: ${{ secrets.CONTEXT_DEV_API_KEY }} + run: 'node tests/live/run-live.mjs --delay 150' diff --git a/package.json b/package.json index dbf81ad..a3914f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "n8n-nodes-context-dev", "version": "3.0.0", - "description": "n8n community node for Context.dev API — brand intelligence, web scraping, industry classification, and AI data extraction", + "description": "n8n community node for Context.dev API \u2014 brand intelligence, web scraping, AI extraction, monitoring, batch, and web databases", "license": "MIT", "homepage": "https://github.com/context-dot-dev/n8n-integration#readme", "keywords": [ @@ -21,18 +21,10 @@ "dev": "n8n-node dev", "lint": "n8n-node lint", "lint:fix": "n8n-node lint --fix", - "test": "jest", + "test": "npm run lint && npm run build", "release": "n8n-node release", - "prepublishOnly": "n8n-node prerelease" - }, - "jest": { - "preset": "ts-jest", - "testEnvironment": "node", - "testMatch": ["**/tests/**/*.test.ts"], - "testPathIgnorePatterns": ["/node_modules/"], - "moduleNameMapper": { - "^n8n-workflow$": "/node_modules/n8n-workflow" - } + "prepublishOnly": "n8n-node prerelease", + "test:live": "node tests/live/run-live.mjs" }, "files": [ "dist" @@ -49,12 +41,9 @@ }, "devDependencies": { "@n8n/node-cli": "*", - "@types/jest": "^29.5.0", "eslint": "9.32.0", - "jest": "^29.5.0", "prettier": "3.6.2", "release-it": "^19.0.4", - "ts-jest": "^29.1.0", "ts-node": "^10.9.2", "typescript": "5.9.2" }, diff --git a/tests/aiDataExtraction.test.ts b/tests/aiDataExtraction.test.ts deleted file mode 100644 index 6742362..0000000 --- a/tests/aiDataExtraction.test.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { aiDataExtractionDescription } from '../nodes/ContextDev/resources/aiDataExtraction'; -import { - getOperations, - urlFor, - methodFor, - getTopField, - getAllTopFields, - getAdditionalField, - usesBodyRouting, - showsForOperations, - additionalFieldShowsFor, - getFixedCollectionValues, - bodyKeyFor, -} from './helpers'; - -describe('aiDataExtraction resource', () => { - // ─── Operations ──────────────────────────────────────────────────────────── - - describe('operations', () => { - it('has exactly 3 operations', () => { - expect(getOperations(aiDataExtractionDescription)).toHaveLength(3); - }); - - it('has no duplicate operation values', () => { - const values = getOperations(aiDataExtractionDescription).map((o) => o.value); - expect(new Set(values).size).toBe(values.length); - }); - - it('every operation has an action defined (required for usableAsTool)', () => { - const missing = getOperations(aiDataExtractionDescription).filter((o) => !o.action); - expect(missing).toHaveLength(0); - }); - - it('operation property has noDataExpression: true', () => { - const op = aiDataExtractionDescription.find((p) => p.name === 'operation'); - expect(op?.noDataExpression).toBe(true); - }); - }); - - // ─── Routing ─────────────────────────────────────────────────────────────── - - describe('routing', () => { - const cases: [string, string, string][] = [ - ['aiQuery', 'POST', '/brand/ai/query'], - ['extractProducts', 'POST', '/brand/ai/products'], - ['extractProduct', 'POST', '/brand/ai/product'], - ]; - - test.each(cases)('%s → %s %s', (op, method, url) => { - expect(methodFor(aiDataExtractionDescription, op)).toBe(method); - expect(urlFor(aiDataExtractionDescription, op)).toBe(url); - }); - }); - - // ─── Required fields ─────────────────────────────────────────────────────── - - describe('required fields', () => { - it('domain is required for aiQuery', () => { - const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => - showsForOperations(f).includes('aiQuery'), - ); - expect(field?.required).toBe(true); - }); - - it('domain is optional for extractProducts (directUrl is an alternative)', () => { - const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => - showsForOperations(f).includes('extractProducts'), - ); - expect(field?.required).toBeFalsy(); - }); - - it('directUrl is optional for extractProducts (domain is an alternative)', () => { - const field = getTopField(aiDataExtractionDescription, 'directUrl'); - expect(field?.required).toBeFalsy(); - }); - - it('url is required for extractProduct', () => { - const field = getTopField(aiDataExtractionDescription, 'url'); - expect(field?.required).toBe(true); - }); - - it('data_to_extract is required for aiQuery', () => { - const field = getTopField(aiDataExtractionDescription, 'data_to_extract'); - expect(field?.required).toBe(true); - }); - }); - - // ─── displayOptions scoping ──────────────────────────────────────────────── - - describe('displayOptions scoping', () => { - it('domain (aiQuery variant) shows only for aiQuery', () => { - const domainField = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => - showsForOperations(f).includes('aiQuery'), - )!; - expect(showsForOperations(domainField)).toEqual(['aiQuery']); - }); - - it('domain (extractProducts variant) shows only for extractProducts', () => { - const domainField = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => - showsForOperations(f).includes('extractProducts'), - )!; - expect(showsForOperations(domainField)).toEqual(['extractProducts']); - }); - - it('directUrl shows only for extractProducts', () => { - const field = getTopField(aiDataExtractionDescription, 'directUrl')!; - expect(showsForOperations(field)).toEqual(['extractProducts']); - }); - - it('url shows only for extractProduct', () => { - const field = getTopField(aiDataExtractionDescription, 'url')!; - expect(showsForOperations(field)).toEqual(['extractProduct']); - }); - - it('data_to_extract shows only for aiQuery', () => { - const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; - expect(showsForOperations(field)).toEqual(['aiQuery']); - }); - }); - - // ─── Field routing ───────────────────────────────────────────────────────── - - describe('field routing — all POST, all body', () => { - it('domain uses body routing', () => { - const field = getAllTopFields(aiDataExtractionDescription, 'domain').find((f) => - showsForOperations(f).includes('aiQuery'), - )!; - expect(usesBodyRouting(field)).toBe(true); - }); - - it('url uses body routing', () => { - const field = getTopField(aiDataExtractionDescription, 'url')!; - expect(usesBodyRouting(field)).toBe(true); - }); - - it('data_to_extract uses body routing', () => { - const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; - expect(usesBodyRouting(field)).toBe(true); - }); - - it('directUrl uses body routing to directUrl', () => { - const field = getTopField(aiDataExtractionDescription, 'directUrl')!; - expect(usesBodyRouting(field)).toBe(true); - expect(bodyKeyFor(field)).toBe('directUrl'); - }); - }); - - // ─── data_to_extract fixedCollection ────────────────────────────────────── - - describe('data_to_extract fixedCollection', () => { - it('is type fixedCollection', () => { - const field = getTopField(aiDataExtractionDescription, 'data_to_extract'); - expect(field?.type).toBe('fixedCollection'); - }); - - it('has multipleValues: true', () => { - const field = getTopField(aiDataExtractionDescription, 'data_to_extract'); - expect(field?.typeOptions?.multipleValues).toBe(true); - }); - - it('datapoint_type options include text, number, date, boolean, list, url', () => { - const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); - const typeField = values.find((v) => v.name === 'datapoint_type'); - expect(typeField).toBeDefined(); - const optionValues = (typeField!.options as { value: string }[]).map((o) => o.value); - expect(optionValues).toContain('text'); - expect(optionValues).toContain('number'); - expect(optionValues).toContain('date'); - expect(optionValues).toContain('boolean'); - expect(optionValues).toContain('list'); - expect(optionValues).toContain('url'); - }); - - it('datapoint_list_type field exists (for when type is list)', () => { - const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); - expect(values.find((v) => v.name === 'datapoint_list_type')).toBeDefined(); - }); - - it('datapoint_list_type offers the full SDK enum incl. list + text', () => { - const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); - const lt = values.find((v) => v.name === 'datapoint_list_type')!; - const opts = (lt.options as { value: string }[]).map((o) => o.value).sort(); - expect(opts).toEqual(['boolean', 'date', 'list', 'number', 'object', 'string', 'text', 'url']); - }); - - it('has datapoint_name, datapoint_description, datapoint_example fields', () => { - const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); - const names = values.map((v) => v.name); - expect(names).toContain('datapoint_name'); - expect(names).toContain('datapoint_description'); - expect(names).toContain('datapoint_example'); - }); - - it('has a datapoint_object_schema field of type json (used when list_type is object)', () => { - const values = getFixedCollectionValues(aiDataExtractionDescription, 'data_to_extract'); - const field = values.find((v) => v.name === 'datapoint_object_schema'); - expect(field).toBeDefined(); - expect(field?.type).toBe('json'); - }); - - it('data_to_extract routing still maps into body.data_to_extract', () => { - const field = getTopField(aiDataExtractionDescription, 'data_to_extract')!; - expect(bodyKeyFor(field)).toBe('data_to_extract'); - }); - }); - - // ─── additionalFields ────────────────────────────────────────────────────── - - describe('additionalFields', () => { - it('specific_pages is type multiOptions', () => { - const field = getAdditionalField(aiDataExtractionDescription, 'specific_pages'); - expect(field?.type).toBe('multiOptions'); - }); - - it('specific_pages options include home_page, pricing, about_us, blog, careers, contact_us, faq', () => { - const field = getAdditionalField(aiDataExtractionDescription, 'specific_pages')!; - const optionValues = (field.options as { value: string }[]).map((o) => o.value); - expect(optionValues).toContain('home_page'); - expect(optionValues).toContain('pricing'); - expect(optionValues).toContain('about_us'); - expect(optionValues).toContain('blog'); - expect(optionValues).toContain('careers'); - expect(optionValues).toContain('contact_us'); - expect(optionValues).toContain('faq'); - }); - - it('specific_pages is scoped to aiQuery only', () => { - const field = getAdditionalField(aiDataExtractionDescription, 'specific_pages')!; - expect(additionalFieldShowsFor(field)).toEqual(['aiQuery']); - }); - - it('maxProducts is scoped to extractProducts only', () => { - const field = getAdditionalField(aiDataExtractionDescription, 'maxProducts')!; - expect(additionalFieldShowsFor(field)).toEqual(['extractProducts']); - }); - - it('timeoutMS exists', () => { - expect(getAdditionalField(aiDataExtractionDescription, 'timeoutMS')).toBeDefined(); - }); - - it('maxAgeMs exists, is type number, and routes to body.maxAgeMs', () => { - const field = getAdditionalField(aiDataExtractionDescription, 'maxAgeMs')!; - expect(field).toBeDefined(); - expect(field.type).toBe('number'); - expect(bodyKeyFor(field)).toBe('maxAgeMs'); - }); - - it('maxAgeMs is scoped to extractProduct and extractProducts only', () => { - const field = getAdditionalField(aiDataExtractionDescription, 'maxAgeMs')!; - const ops = additionalFieldShowsFor(field); - expect(ops.sort()).toEqual(['extractProduct', 'extractProducts'].sort()); - }); - }); - - // ─── All fields have defaults ────────────────────────────────────────────── - - describe('all fields have a default value', () => { - it('no top-level field is missing default', () => { - const missing = aiDataExtractionDescription - .filter((p) => p.name !== 'operation') - .filter((p) => p.default === undefined); - expect(missing.map((p) => p.name)).toEqual([]); - }); - }); - - describe('specific_pages (aiQuery)', () => { - const field = () => getTopField(aiDataExtractionDescription, 'specificPages')!; - - it('is a collection scoped to aiQuery', () => { - expect(field()).toBeDefined(); - expect(field().type).toBe('collection'); - expect(showsForOperations(field())).toEqual(['aiQuery']); - }); - - it('exposes all 9 SDK page flags, each routing specific_pages.', () => { - const opts = field().options as { name: string; routing: { send: { property: string } } }[]; - const names = opts.map((o) => o.name).sort(); - expect(names).toEqual( - [ - 'about_us', - 'blog', - 'careers', - 'contact_us', - 'faq', - 'home_page', - 'pricing', - 'privacy_policy', - 'terms_and_conditions', - ].sort(), - ); - for (const o of opts) - expect(o.routing.send.property).toBe(`specific_pages.${o.name}`); - }); - }); -}); diff --git a/tests/brand.test.ts b/tests/brand.test.ts deleted file mode 100644 index 3a35b1a..0000000 --- a/tests/brand.test.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { brandDescription } from '../nodes/ContextDev/resources/brand'; -import { - getOperations, - urlFor, - methodFor, - getTopField, - getAllTopFields, - getAdditionalField, - usesQsRouting, - showsForOperations, - additionalFieldShowsFor, - qsKeyFor, -} from './helpers'; - -describe('brand resource', () => { - // ─── Operations ──────────────────────────────────────────────────────────── - - describe('operations', () => { - it('has exactly 7 operations', () => { - expect(getOperations(brandDescription)).toHaveLength(7); - }); - - it('has no duplicate operation values', () => { - const values = getOperations(brandDescription).map((o) => o.value); - expect(new Set(values).size).toBe(values.length); - }); - - it('every operation has an action defined (required for usableAsTool)', () => { - const missing = getOperations(brandDescription).filter((o) => !o.action); - expect(missing).toHaveLength(0); - }); - - it('operation property has noDataExpression: true', () => { - const op = brandDescription.find((p) => p.name === 'operation'); - expect(op?.noDataExpression).toBe(true); - }); - }); - - // ─── Routing ─────────────────────────────────────────────────────────────── - - describe('routing', () => { - const cases: [string, string, string][] = [ - ['retrieve', 'GET', '/brand/retrieve'], - ['retrieveByEmail', 'GET', '/brand/retrieve-by-email'], - ['retrieveByName', 'GET', '/brand/retrieve-by-name'], - ['retrieveByTicker', 'GET', '/brand/retrieve-by-ticker'], - ['retrieveByIsin', 'GET', '/brand/retrieve-by-isin'], - ['retrieveSimplified', 'GET', '/brand/retrieve-simplified'], - ['identifyFromTransaction', 'GET', '/brand/transaction_identifier'], - ]; - - test.each(cases)('%s → %s %s', (op, method, url) => { - expect(methodFor(brandDescription, op)).toBe(method); - expect(urlFor(brandDescription, op)).toBe(url); - }); - }); - - // ─── Required fields ─────────────────────────────────────────────────────── - - describe('required fields exist and are marked required', () => { - const cases: [string][] = [ - ['domain'], - ['email'], - ['name'], - ['ticker'], - ['isin'], - ['transaction_info'], - ]; - - test.each(cases)('%s is required', (fieldName) => { - const field = getTopField(brandDescription, fieldName); - expect(field).toBeDefined(); - expect(field?.required).toBe(true); - }); - }); - - // ─── displayOptions scoping ──────────────────────────────────────────────── - - describe('displayOptions scoping — fields show for correct operations', () => { - it('domain shows for retrieve and retrieveSimplified only', () => { - const fields = getAllTopFields(brandDescription, 'domain'); - // Could be one field covering both, or two separate fields - const allOps = fields.flatMap((f) => showsForOperations(f)); - expect(allOps).toContain('retrieve'); - expect(allOps).toContain('retrieveSimplified'); - expect(allOps).not.toContain('retrieveByEmail'); - expect(allOps).not.toContain('retrieveByTicker'); - expect(allOps).not.toContain('identifyFromTransaction'); - }); - - it('email shows only for retrieveByEmail', () => { - const field = getTopField(brandDescription, 'email')!; - expect(showsForOperations(field)).toEqual(['retrieveByEmail']); - }); - - it('name shows only for retrieveByName', () => { - const field = getTopField(brandDescription, 'name')!; - expect(showsForOperations(field)).toEqual(['retrieveByName']); - }); - - it('ticker shows only for retrieveByTicker', () => { - const field = getTopField(brandDescription, 'ticker')!; - expect(showsForOperations(field)).toEqual(['retrieveByTicker']); - }); - - it('isin shows only for retrieveByIsin', () => { - const field = getTopField(brandDescription, 'isin')!; - expect(showsForOperations(field)).toEqual(['retrieveByIsin']); - }); - - it('transaction_info shows only for identifyFromTransaction', () => { - const field = getTopField(brandDescription, 'transaction_info')!; - expect(showsForOperations(field)).toEqual(['identifyFromTransaction']); - }); - }); - - // ─── Field routing ───────────────────────────────────────────────────────── - - describe('all required fields use qs routing (GET params)', () => { - const cases = ['domain', 'email', 'name', 'ticker', 'isin', 'transaction_info']; - - test.each(cases)('%s uses qs routing', (fieldName) => { - const field = getTopField(brandDescription, fieldName)!; - expect(usesQsRouting(field)).toBe(true); - }); - }); - - // ─── Field types ─────────────────────────────────────────────────────────── - - describe('field types', () => { - it('domain is type string', () => { - expect(getTopField(brandDescription, 'domain')?.type).toBe('string'); - }); - - it('email is type string', () => { - expect(getTopField(brandDescription, 'email')?.type).toBe('string'); - }); - - it('ticker is type string', () => { - expect(getTopField(brandDescription, 'ticker')?.type).toBe('string'); - }); - - it('timeoutMS in additionalFields is type number', () => { - expect(getAdditionalField(brandDescription, 'timeoutMS')?.type).toBe('number'); - }); - - it('maxSpeed in additionalFields is type boolean', () => { - expect(getAdditionalField(brandDescription, 'maxSpeed')?.type).toBe('boolean'); - }); - - it('maxAgeMs in additionalFields is type number', () => { - expect(getAdditionalField(brandDescription, 'maxAgeMs')?.type).toBe('number'); - }); - }); - - // ─── additionalFields ────────────────────────────────────────────────────── - - describe('additionalFields', () => { - it('contains maxSpeed (not old speed_optimized)', () => { - expect(getAdditionalField(brandDescription, 'maxSpeed')).toBeDefined(); - expect(getAdditionalField(brandDescription, 'speed_optimized')).toBeUndefined(); - }); - - it('contains maxAgeMs', () => { - expect(getAdditionalField(brandDescription, 'maxAgeMs')).toBeDefined(); - }); - - it('contains timeoutMS', () => { - expect(getAdditionalField(brandDescription, 'timeoutMS')).toBeDefined(); - }); - - it('contains force_language', () => { - expect(getAdditionalField(brandDescription, 'force_language')).toBeDefined(); - }); - - it('force_language exposes the full SDK language enum (120 values incl. afrikaans/welsh/zulu)', () => { - const f = getAdditionalField(brandDescription, 'force_language')!; - const values = (f.options as { value: string }[]).map((o) => o.value); - expect(values.length).toBe(120); - for (const v of ['afrikaans', 'welsh', 'zulu', 'english', 'latin', 'yoruba']) - expect(values).toContain(v); - }); - - it('transaction-only fields (mcc, city, phone, high_confidence_only) have /operation scoped to identifyFromTransaction', () => { - const txFields = ['mcc', 'city', 'phone', 'high_confidence_only']; - for (const name of txFields) { - const field = getAdditionalField(brandDescription, name); - expect(field).toBeDefined(); - expect(additionalFieldShowsFor(field!)).toEqual(['identifyFromTransaction']); - } - }); - - it('country_gl is scoped to identifyFromTransaction and retrieveByName (SDK supports both)', () => { - const field = getAdditionalField(brandDescription, 'country_gl'); - expect(field).toBeDefined(); - const scope = additionalFieldShowsFor(field!); - expect(scope).toEqual(expect.arrayContaining(['identifyFromTransaction', 'retrieveByName'])); - expect(scope).toHaveLength(2); - }); - - it('country_gl is not scoped to operations whose SDK params lack it (retrieve, retrieveByEmail, retrieveByIsin, retrieveByTicker, retrieveSimplified)', () => { - const field = getAdditionalField(brandDescription, 'country_gl')!; - const scope = additionalFieldShowsFor(field); - expect(scope).not.toContain('retrieve'); - expect(scope).not.toContain('retrieveByEmail'); - expect(scope).not.toContain('retrieveByIsin'); - expect(scope).not.toContain('retrieveByTicker'); - expect(scope).not.toContain('retrieveSimplified'); - }); - - it('phone is a string routing qs key "phone"', () => { - const field = getAdditionalField(brandDescription, 'phone')!; - expect(field.type).toBe('string'); - expect(qsKeyFor(field)).toBe('phone'); - }); - - it('high_confidence_only is a boolean routing qs key "high_confidence_only"', () => { - const field = getAdditionalField(brandDescription, 'high_confidence_only')!; - expect(field.type).toBe('boolean'); - expect(qsKeyFor(field)).toBe('high_confidence_only'); - }); - - it('ticker_exchange is scoped to retrieveByTicker only', () => { - const field = getAdditionalField(brandDescription, 'ticker_exchange'); - expect(field).toBeDefined(); - expect(additionalFieldShowsFor(field!)).toEqual(['retrieveByTicker']); - }); - - it('ticker_exchange contains all 72 SDK exchange codes, defaulting to NASDAQ', () => { - const field = getAdditionalField(brandDescription, 'ticker_exchange')!; - const values = (field.options as { value: string }[]).map((o) => o.value); - const expectedCodes = [ - 'AMEX', 'AMS', 'AQS', 'ASX', 'ATH', 'BER', 'BME', 'BRU', 'BSE', 'BUD', - 'BUE', 'BVC', 'CBOE', 'CNQ', 'CPH', 'DFM', 'DOH', 'DUB', 'DUS', 'DXE', - 'EGX', 'FSX', 'HAM', 'HEL', 'HKSE', 'HOSE', 'ICE', 'IOB', 'IST', 'JKT', - 'JNB', 'JPX', 'KLS', 'KOE', 'KSC', 'KUW', 'LIS', 'LSE', 'MCX', 'MEX', - 'MIL', 'MUN', 'NASDAQ', 'NEO', 'NSE', 'NYSE', 'NZE', 'OSL', 'OTC', 'PAR', - 'PNK', 'PRA', 'RIS', 'SAO', 'SAU', 'SES', 'SET', 'SGO', 'SHH', 'SHZ', - 'SIX', 'STO', 'STU', 'TAI', 'TAL', 'TLV', 'TSX', 'TSXV', 'TWO', 'VIE', - 'WSE', 'XETRA', - ]; - expect(expectedCodes).toHaveLength(72); - expect(values.sort()).toEqual([...expectedCodes].sort()); - expect(field.default).toBe('NASDAQ'); - }); - - it('ticker_exchange does not contain invalid codes SGX or SSE', () => { - const field = getAdditionalField(brandDescription, 'ticker_exchange')!; - const values = (field.options as { value: string }[]).map((o) => o.value); - expect(values).not.toContain('SGX'); - expect(values).not.toContain('SSE'); - }); - - it('ticker_exchange includes representative previously-missing codes', () => { - const field = getAdditionalField(brandDescription, 'ticker_exchange')!; - const values = (field.options as { value: string }[]).map((o) => o.value); - for (const code of ['LIS', 'MIL', 'PAR', 'SIX', 'XETRA', 'OTC', 'TSXV', 'CBOE']) { - expect(values).toContain(code); - } - }); - }); - - // ─── Routing key names match API param names ────────────────────────────── - - describe('routing qs key names match API param names', () => { - it('domain field sends key "domain"', () => { - const field = getTopField(brandDescription, 'domain')!; - expect(qsKeyFor(field)).toBe('domain'); - }); - - it('email field sends key "email"', () => { - const field = getTopField(brandDescription, 'email')!; - expect(qsKeyFor(field)).toBe('email'); - }); - - it('name field sends key "name"', () => { - const field = getTopField(brandDescription, 'name')!; - expect(qsKeyFor(field)).toBe('name'); - }); - - it('ticker field sends key "ticker"', () => { - const field = getTopField(brandDescription, 'ticker')!; - expect(qsKeyFor(field)).toBe('ticker'); - }); - - it('isin field sends key "isin"', () => { - const field = getTopField(brandDescription, 'isin')!; - expect(qsKeyFor(field)).toBe('isin'); - }); - - it('transaction_info field sends key "transaction_info"', () => { - const field = getTopField(brandDescription, 'transaction_info')!; - expect(qsKeyFor(field)).toBe('transaction_info'); - }); - }); - - // ─── All fields have defaults ────────────────────────────────────────────── - - describe('all fields have a default value', () => { - it('no top-level field is missing default', () => { - const missing = brandDescription - .filter((p) => p.name !== 'operation') - .filter((p) => p.default === undefined); - expect(missing.map((p) => p.name)).toEqual([]); - }); - }); - - // ─── No brand.dev URLs ───────────────────────────────────────────────────── - - describe('no brand.dev references', () => { - it('no operation description contains brand.dev', () => { - const text = getOperations(brandDescription) - .map((o) => o.description ?? '') - .join(' '); - expect(text).not.toContain('brand.dev'); - }); - }); -}); diff --git a/tests/credentials.test.ts b/tests/credentials.test.ts deleted file mode 100644 index 20a9d50..0000000 --- a/tests/credentials.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ContextdevApi } from '../credentials/ContextdevApi.credentials'; - -describe('ContextdevApi credential', () => { - const cred = new ContextdevApi(); - - // ─── Identity ────────────────────────────────────────────────────────────── - - describe('identity', () => { - it('name is contextdevApi', () => { - expect(cred.name).toBe('contextdevApi'); - }); - - it('name is NOT branddevApi (old name)', () => { - expect(cred.name).not.toBe('branddevApi'); - }); - - it('displayName is Context.dev API', () => { - expect(cred.displayName).toBe('Context.dev API'); - }); - - it('documentationUrl points to context.dev not brand.dev', () => { - expect(cred.documentationUrl).toContain('context.dev'); - expect(cred.documentationUrl).not.toContain('brand.dev'); - }); - }); - - // ─── Properties ──────────────────────────────────────────────────────────── - - describe('properties', () => { - it('has an apiKey field', () => { - const field = cred.properties.find((p) => p.name === 'apiKey'); - expect(field).toBeDefined(); - }); - - it('apiKey is type string', () => { - const field = cred.properties.find((p) => p.name === 'apiKey')!; - expect(field.type).toBe('string'); - }); - - it('apiKey is password type (masked in UI)', () => { - const field = cred.properties.find((p) => p.name === 'apiKey')!; - expect(field.typeOptions?.password).toBe(true); - }); - - it('apiKey has a default value', () => { - const field = cred.properties.find((p) => p.name === 'apiKey')!; - expect(field.default).toBeDefined(); - }); - }); - - // ─── Authentication ──────────────────────────────────────────────────────── - - describe('authentication', () => { - it('uses generic auth type', () => { - expect(cred.authenticate?.type).toBe('generic'); - }); - - it('injects Authorization Bearer header', () => { - const headers = (cred.authenticate as { properties: { headers: Record } }) - .properties?.headers; - expect(headers?.Authorization).toBeDefined(); - expect(headers?.Authorization).toContain('Bearer'); - }); - - it('injects integration_name: n8n header', () => { - const headers = (cred.authenticate as { properties: { headers: Record } }) - .properties?.headers; - expect(headers?.integration_name).toBe('n8n'); - }); - }); - - // ─── Test request ────────────────────────────────────────────────────────── - - describe('credential test request', () => { - const testReq = (cred.test as { request: { baseURL?: string; url?: string } }).request; - - it('test baseURL points to api.context.dev', () => { - expect(testReq.baseURL).toContain('context.dev'); - }); - - it('test baseURL does NOT point to api.brand.dev', () => { - expect(testReq.baseURL).not.toContain('brand.dev'); - }); - - it('test URL is a brand retrieve endpoint', () => { - expect(testReq.url).toContain('/brand/retrieve'); - }); - }); -}); diff --git a/tests/helpers.ts b/tests/helpers.ts deleted file mode 100644 index 9b8fb7f..0000000 --- a/tests/helpers.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type { INodeProperties, INodePropertyOptions } from 'n8n-workflow'; - -// ─── Operation helpers ─────────────────────────────────────────────────────── - -export function getOperationProp(desc: INodeProperties[]): INodeProperties { - const op = desc.find((p) => p.name === 'operation' && p.type === 'options'); - if (!op) throw new Error('No operation property found'); - return op; -} - -export function getOperations(desc: INodeProperties[]): INodePropertyOptions[] { - return getOperationProp(desc).options as INodePropertyOptions[]; -} - -export function getOperation(desc: INodeProperties[], value: string): INodePropertyOptions { - const op = getOperations(desc).find((o) => o.value === value); - if (!op) throw new Error(`Operation '${value}' not found`); - return op; -} - -export function operationExists(desc: INodeProperties[], value: string): boolean { - return getOperations(desc).some((o) => o.value === value); -} - -export function allUrls(desc: INodeProperties[]): string[] { - return getOperations(desc) - .map((o) => o.routing?.request?.url as string) - .filter(Boolean); -} - -export function methodFor(desc: INodeProperties[], value: string): string { - return getOperation(desc, value).routing?.request?.method as string; -} - -export function urlFor(desc: INodeProperties[], value: string): string { - return getOperation(desc, value).routing?.request?.url as string; -} - -// ─── Field helpers ──────────────────────────────────────────────────────────── - -/** Top-level field by name */ -export function getTopField(desc: INodeProperties[], name: string): INodeProperties | undefined { - return desc.find((p) => p.name === name); -} - -/** Field inside any additionalFields collection (supports multiple collections) */ -export function getAdditionalField( - desc: INodeProperties[], - name: string, -): INodeProperties | undefined { - const collections = desc.filter((p) => p.name === 'additionalFields'); - for (const af of collections) { - if (!af.options) continue; - const found = (af.options as INodeProperties[]).find((p) => p.name === name); - if (found) return found; - } - return undefined; -} - -/** All top-level fields with a given name (there can be multiple for GET vs POST routing) */ -export function getAllTopFields(desc: INodeProperties[], name: string): INodeProperties[] { - return desc.filter((p) => p.name === name); -} - -/** additionalField with a given name scoped to a specific operation (disambiguates duplicates) */ -export function getAdditionalFieldFor( - desc: INodeProperties[], - name: string, - op: string, -): INodeProperties | undefined { - const collections = desc.filter((p) => p.name === 'additionalFields'); - for (const af of collections) { - const found = (af.options as INodeProperties[] | undefined)?.find( - (p) => p.name === name && additionalFieldShowsFor(p).includes(op), - ); - if (found) return found; - } - return undefined; -} - -export function usesQsRouting(field: INodeProperties): boolean { - return !!field.routing?.request?.qs; -} - -export function usesBodyRouting(field: INodeProperties): boolean { - return !!field.routing?.request?.body; -} - -/** Check which operations a top-level field's displayOptions.show.operation includes */ -export function showsForOperations(field: INodeProperties): string[] { - const show = field.displayOptions?.show as Record | undefined; - return show?.operation ?? []; -} - -/** Check which operations an additionalField's displayOptions.show['/operation'] includes */ -export function additionalFieldShowsFor(field: INodeProperties): string[] { - const show = field.displayOptions?.show as Record | undefined; - return show?.['/operation'] ?? []; -} - -// ─── fixedCollection helpers ────────────────────────────────────────────────── - -export function getFixedCollectionValues(desc: INodeProperties[], name: string): INodeProperties[] { - const field = desc.find((p) => p.name === name); - if (!field?.options) return []; - const group = (field.options as { values: INodeProperties[] }[])[0]; - return group?.values ?? []; -} - -// ─── Sweep helpers ──────────────────────────────────────────────────────────── - -/** Get the qs key name a field sends (e.g. { domain: '...' } → 'domain') */ -export function qsKeyFor(field: INodeProperties): string | undefined { - const qs = field.routing?.request?.qs; - if (!qs) return undefined; - return Object.keys(qs)[0]; -} - -/** Get the body key name a field sends (e.g. { domain: '...' } → 'domain') */ -export function bodyKeyFor(field: INodeProperties): string | undefined { - const body = field.routing?.request?.body; - if (!body) return undefined; - return Object.keys(body)[0]; -} - -/** Get the arrayFormat set on a field's routing.request (controls qs array serialization) */ -export function arrayFormatFor(field: INodeProperties): string | undefined { - return (field.routing?.request as { arrayFormat?: string } | undefined)?.arrayFormat; -} - -/** Get the routing.send.property path a field routes to (e.g. 'markdownOptions.enabled') */ -export function sendPropertyFor(field: INodeProperties): string | undefined { - return (field.routing?.send as { property?: string } | undefined)?.property; -} - -/** Get the routing.send.type a field uses (e.g. 'body') */ -export function sendTypeFor(field: INodeProperties): string | undefined { - return (field.routing?.send as { type?: string } | undefined)?.type; -} - -/** Every INodeProperties in a description, including nested options */ -export function allFields(desc: INodeProperties[]): INodeProperties[] { - const result: INodeProperties[] = []; - for (const prop of desc) { - result.push(prop); - if (prop.type === 'collection' || prop.type === 'fixedCollection') { - const nested = prop.options as INodeProperties[] | undefined; - if (nested) result.push(...nested); - } - } - return result; -} diff --git a/tests/industry.test.ts b/tests/industry.test.ts deleted file mode 100644 index 0258c28..0000000 --- a/tests/industry.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { industryDescription } from '../nodes/ContextDev/resources/industry'; -import { - getOperations, - urlFor, - methodFor, - allUrls, - getTopField, - getAdditionalField, - usesQsRouting, - showsForOperations, - additionalFieldShowsFor, - qsKeyFor, -} from './helpers'; - -describe('industry resource', () => { - // ─── Operations ──────────────────────────────────────────────────────────── - - describe('operations', () => { - it('has exactly 2 operations', () => { - expect(getOperations(industryDescription)).toHaveLength(2); - }); - - it('has no duplicate operation values', () => { - const values = getOperations(industryDescription).map((o) => o.value); - expect(new Set(values).size).toBe(values.length); - }); - - it('every operation has an action defined', () => { - const missing = getOperations(industryDescription).filter((o) => !o.action); - expect(missing).toHaveLength(0); - }); - - it('operation property has noDataExpression: true', () => { - const op = industryDescription.find((p) => p.name === 'operation'); - expect(op?.noDataExpression).toBe(true); - }); - }); - - // ─── Routing — correct URLs ──────────────────────────────────────────────── - - describe('routing — correct URLs', () => { - it('retrieveNaics → GET /web/naics', () => { - expect(methodFor(industryDescription, 'retrieveNaics')).toBe('GET'); - expect(urlFor(industryDescription, 'retrieveNaics')).toBe('/web/naics'); - }); - - it('retrieveSic → GET /web/sic', () => { - expect(methodFor(industryDescription, 'retrieveSic')).toBe('GET'); - expect(urlFor(industryDescription, 'retrieveSic')).toBe('/web/sic'); - }); - }); - - // ─── Migration — old wrong URLs are gone ────────────────────────────────── - - describe('migration — old wrong URLs do not exist', () => { - it('no operation routes to /brand/naics (old)', () => { - expect(allUrls(industryDescription)).not.toContain('/brand/naics'); - }); - - it('no operation routes to /brand/sic (old)', () => { - expect(allUrls(industryDescription)).not.toContain('/brand/sic'); - }); - }); - - // ─── Required fields ─────────────────────────────────────────────────────── - - describe('required fields', () => { - it('input field exists and is required', () => { - const field = getTopField(industryDescription, 'input'); - expect(field).toBeDefined(); - expect(field?.required).toBe(true); - }); - - it('input is type string', () => { - expect(getTopField(industryDescription, 'input')?.type).toBe('string'); - }); - - it('input uses qs routing', () => { - const field = getTopField(industryDescription, 'input')!; - expect(usesQsRouting(field)).toBe(true); - }); - }); - - // ─── displayOptions scoping ──────────────────────────────────────────────── - - describe('displayOptions scoping', () => { - it('input shows for both retrieveNaics and retrieveSic', () => { - const field = getTopField(industryDescription, 'input')!; - const ops = showsForOperations(field); - expect(ops).toContain('retrieveNaics'); - expect(ops).toContain('retrieveSic'); - }); - - it('SIC type param only shows for retrieveSic', () => { - const field = getAdditionalField(industryDescription, 'type')!; - expect(additionalFieldShowsFor(field)).toEqual(['retrieveSic']); - }); - }); - - // ─── additionalFields ────────────────────────────────────────────────────── - - describe('additionalFields', () => { - it('contains maxResults', () => { - expect(getAdditionalField(industryDescription, 'maxResults')).toBeDefined(); - }); - - it('maxResults is type number', () => { - expect(getAdditionalField(industryDescription, 'maxResults')?.type).toBe('number'); - }); - - it('contains minResults', () => { - expect(getAdditionalField(industryDescription, 'minResults')).toBeDefined(); - }); - - it('SIC dataset type param has original_sic and latest_sec options', () => { - const field = getAdditionalField(industryDescription, 'type')!; - expect(field).toBeDefined(); - const values = (field.options as { value: string }[]).map((o) => o.value); - expect(values).toContain('original_sic'); - expect(values).toContain('latest_sec'); - }); - - it('contains timeoutMS', () => { - expect(getAdditionalField(industryDescription, 'timeoutMS')).toBeDefined(); - }); - }); - - // ─── Routing key names ──────────────────────────────────────────────────── - - describe('routing key names match API param names', () => { - it('input field sends key "input"', () => { - const field = getTopField(industryDescription, 'input')!; - expect(qsKeyFor(field)).toBe('input'); - }); - }); - - // ─── All fields have defaults ────────────────────────────────────────────── - - describe('all fields have a default value', () => { - it('no top-level field is missing default', () => { - const missing = industryDescription - .filter((p) => p.name !== 'operation') - .filter((p) => p.default === undefined); - expect(missing.map((p) => p.name)).toEqual([]); - }); - }); -}); diff --git a/tests/monitors.test.ts b/tests/monitors.test.ts deleted file mode 100644 index 8095d35..0000000 --- a/tests/monitors.test.ts +++ /dev/null @@ -1,390 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; -import { monitorsDescription } from '../nodes/ContextDev/resources/monitors'; -import { - getOperations, - operationExists, - urlFor, - methodFor, - getTopField, - getAdditionalField, - showsForOperations, -} from './helpers'; - -// ─── local helpers for send.property routing (monitors builds nested bodies this way) ───────── -const sendOf = (f?: INodeProperties) => - (f?.routing as { send?: { type?: string; property?: string; value?: string } } | undefined)?.send; - -/** find a field by name anywhere: top-level or inside any collection's options */ -const anyField = (name: string): INodeProperties | undefined => - getTopField(monitorsDescription, name) ?? getAdditionalField(monitorsDescription, name); - -describe('monitors resource', () => { - // ─── operations ──────────────────────────────────────────────────────────── - const OPS: [value: string, method: string, urlIncludes: string][] = [ - ['create', 'POST', '/monitors'], - ['get', 'GET', '/monitors/'], - ['update', 'PATCH', '/monitors/'], - ['list', 'GET', '/monitors'], - ['delete', 'DELETE', '/monitors/'], - ['run', 'POST', '/run'], - ['listAccountChanges', 'GET', '/monitors/changes'], - ['listAccountRuns', 'GET', '/monitors/runs'], - ['listChanges', 'GET', '/changes'], - ['listRuns', 'GET', '/runs'], - ['getChange', 'GET', '/monitors/changes/'], - ]; - - describe('operations', () => { - it('has exactly 11 operations', () => { - expect(getOperations(monitorsDescription)).toHaveLength(11); - }); - - it('has no duplicate operation values', () => { - const vals = getOperations(monitorsDescription).map((o) => o.value); - expect(new Set(vals).size).toBe(vals.length); - }); - - it('every operation defines an action (required for usableAsTool)', () => { - for (const o of getOperations(monitorsDescription)) expect(o.action).toBeTruthy(); - }); - - it.each(OPS)('%s → %s and url contains "%s"', (value, method, urlIncludes) => { - expect(operationExists(monitorsDescription, value)).toBe(true); - expect(methodFor(monitorsDescription, value)).toBe(method); - expect(urlFor(monitorsDescription, value)).toContain(urlIncludes); - }); - }); - - // ─── path params ───────────────────────────────────────────────────────────── - describe('path params', () => { - it('monitorId is required and scoped to the id-bearing ops', () => { - const f = getTopField(monitorsDescription, 'monitorId')!; - expect(f).toBeDefined(); - expect(f.required).toBe(true); - expect(showsForOperations(f).sort()).toEqual( - ['delete', 'get', 'listChanges', 'listRuns', 'run', 'update'].sort(), - ); - }); - - it('changeId is required and scoped to getChange', () => { - const f = getTopField(monitorsDescription, 'changeId')!; - expect(f).toBeDefined(); - expect(f.required).toBe(true); - expect(showsForOperations(f)).toEqual(['getChange']); - }); - - it('id-bearing op URLs interpolate the id parameter', () => { - for (const op of ['get', 'update', 'delete', 'run', 'listChanges', 'listRuns']) - expect(urlFor(monitorsDescription, op)).toContain('$parameter["monitorId"]'); - expect(urlFor(monitorsDescription, 'getChange')).toContain('$parameter["changeId"]'); - }); - }); - - // ─── create: discriminators + nested body via send.property ────────────────── - describe('create — required core fields', () => { - it('name is required, scoped to create, sends body "name"', () => { - const f = getTopField(monitorsDescription, 'name')!; - expect(f.required).toBe(true); - expect(showsForOperations(f)).toEqual(['create']); - expect(sendOf(f)).toMatchObject({ type: 'body', property: 'name' }); - }); - - it('targetType is an options discriminator (page/sitemap/extract) → target.type', () => { - const f = getTopField(monitorsDescription, 'targetType')!; - expect(f.type).toBe('options'); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'extract', - 'page', - 'sitemap', - ]); - expect(sendOf(f)).toMatchObject({ type: 'body', property: 'target.type' }); - expect(showsForOperations(f)).toEqual(['create']); - }); - - it('targetUrl is required for create and sends target.url', () => { - const f = getTopField(monitorsDescription, 'targetUrl')!; - expect(f.required).toBe(true); - expect(sendOf(f)).toMatchObject({ type: 'body', property: 'target.url' }); - }); - - it('changeDetectionType is an options discriminator (exact/semantic) → change_detection.type', () => { - const f = getTopField(monitorsDescription, 'changeDetectionType')!; - expect(f.type).toBe('options'); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'exact', - 'semantic', - ]); - expect(sendOf(f)).toMatchObject({ type: 'body', property: 'change_detection.type' }); - }); - - it('schedule frequency + unit send schedule.frequency / schedule.unit', () => { - const freq = getTopField(monitorsDescription, 'scheduleFrequency')!; - const unit = getTopField(monitorsDescription, 'scheduleUnit')!; - expect(freq.type).toBe('number'); - expect(sendOf(freq)).toMatchObject({ type: 'body', property: 'schedule.frequency' }); - expect((unit.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'days', - 'hours', - 'minutes', - ]); - expect(sendOf(unit)).toMatchObject({ type: 'body', property: 'schedule.unit' }); - }); - - it('schedule.type is sent as the constant "interval" via a hidden field', () => { - const f = getTopField(monitorsDescription, 'scheduleType')!; - expect(f.type).toBe('hidden'); - expect(f.default).toBe('interval'); - expect(sendOf(f)).toMatchObject({ type: 'body', property: 'schedule.type' }); - }); - }); - - describe('create — conditional target sub-fields', () => { - it('normalizeWhitespace shows only for target page → target.normalize_whitespace', () => { - const f = getTopField(monitorsDescription, 'normalizeWhitespace')!; - expect(f.type).toBe('boolean'); - expect((f.displayOptions?.show as Record).targetType).toEqual(['page']); - expect(sendOf(f)).toMatchObject({ property: 'target.normalize_whitespace' }); - }); - - it('followSubdomains + instructions + schema show only for target extract', () => { - for (const [name, prop] of [ - ['followSubdomains', 'target.follow_subdomains'], - ['extractPrompt', 'target.instructions'], - ['extractSchema', 'target.schema'], - ] as const) { - const f = getTopField(monitorsDescription, name)!; - expect((f.displayOptions?.show as Record).targetType).toEqual(['extract']); - expect(sendOf(f)!.property).toBe(prop); - } - }); - - it('extractSchema parses JSON before sending', () => { - const f = getTopField(monitorsDescription, 'extractSchema')!; - expect(f.type).toBe('json'); - expect(sendOf(f)!.value).toContain('JSON.parse'); - }); - - it('extract max depth / max pages show only for target extract, as numbers', () => { - for (const [name, prop] of [ - ['extractMaxDepth', 'target.max_depth'], - ['extractMaxPages', 'target.max_pages'], - ] as const) { - const f = getTopField(monitorsDescription, name)!; - expect(f).toBeDefined(); - expect(f.type).toBe('number'); - expect((f.displayOptions?.show as Record).targetType).toEqual(['extract']); - expect(sendOf(f)!.property).toBe(prop); - } - }); - - it('sitemap exclude/include/max_urls show only for target sitemap', () => { - const exclude = getTopField(monitorsDescription, 'sitemapExclude')!; - expect(exclude).toBeDefined(); - expect((exclude.displayOptions?.show as Record).targetType).toEqual([ - 'sitemap', - ]); - expect(sendOf(exclude)!.property).toBe('target.exclude'); - expect(sendOf(exclude)!.value).toContain('split'); - - const include = getTopField(monitorsDescription, 'sitemapInclude')!; - expect(include).toBeDefined(); - expect((include.displayOptions?.show as Record).targetType).toEqual([ - 'sitemap', - ]); - expect(sendOf(include)!.property).toBe('target.include'); - expect(sendOf(include)!.value).toContain('split'); - - const maxUrls = getTopField(monitorsDescription, 'sitemapMaxUrls')!; - expect(maxUrls).toBeDefined(); - expect(maxUrls.type).toBe('number'); - expect((maxUrls.displayOptions?.show as Record).targetType).toEqual([ - 'sitemap', - ]); - expect(sendOf(maxUrls)!.property).toBe('target.max_urls'); - }); - }); - - describe('create — conditional semantic fields', () => { - it('semanticQuery shows only for semantic detection → change_detection.query, required', () => { - const f = getTopField(monitorsDescription, 'semanticQuery')!; - expect((f.displayOptions?.show as Record).changeDetectionType).toEqual([ - 'semantic', - ]); - expect(f.required).toBe(true); - expect(sendOf(f)!.property).toBe('change_detection.query'); - }); - - it('confidenceThreshold shows only for semantic → change_detection.confidence_threshold', () => { - const f = getTopField(monitorsDescription, 'confidenceThreshold')!; - expect(f.type).toBe('number'); - expect((f.displayOptions?.show as Record).changeDetectionType).toEqual([ - 'semantic', - ]); - expect(sendOf(f)!.property).toBe('change_detection.confidence_threshold'); - }); - }); - - describe('create — optional webhook + tags', () => { - it('webhookUrl sends webhook.url', () => { - const f = getAdditionalField(monitorsDescription, 'webhookUrl')!; - expect(f).toBeDefined(); - expect(sendOf(f)!.property).toBe('webhook.url'); - }); - - it('webhookSecret (create) sends webhook.secret', () => { - const f = getAdditionalField(monitorsDescription, 'webhookSecret')!; - expect(f).toBeDefined(); - expect(sendOf(f)!.property).toBe('webhook.secret'); - }); - - it('tags splits csv into a string array on body key "tags"', () => { - const f = getAdditionalField(monitorsDescription, 'tags')!; - expect(f).toBeDefined(); - const body = f.routing?.request?.body as Record | undefined; - const send = sendOf(f); - const expr = body?.tags ?? send?.value ?? ''; - expect(expr).toContain('split'); - }); - }); - - // ─── update: all-optional mutable fields ───────────────────────────────────── - describe('update fields', () => { - it('status is options active/paused → body "status"', () => { - const f = anyField('updStatus')!; - expect(f).toBeDefined(); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'active', - 'paused', - ]); - expect(sendOf(f)!.property).toBe('status'); - }); - - it('update fields collection is scoped to the update operation only', () => { - const coll = monitorsDescription.find( - (p) => p.name === 'additionalFields' && showsForOperations(p).includes('update'), - )!; - expect(showsForOperations(coll)).toEqual(['update']); - }); - - it.each([ - ['updName', 'name'], - ['updWebhookUrl', 'webhook.url'], - ['updWebhookSecret', 'webhook.secret'], - ['updQuery', 'change_detection.query'], - ['updExtractInstructions', 'target.instructions'], - ['updExtractSchema', 'target.schema'], - ])('update field %s sends body path %s', (name, prop) => { - const f = anyField(name)!; - expect(f).toBeDefined(); - expect(sendOf(f)!.property).toBe(prop); - }); - - it('update extract schema is JSON and parses before sending', () => { - const f = anyField('updExtractSchema')!; - expect(f.type).toBe('json'); - expect(sendOf(f)!.value).toContain('JSON.parse'); - }); - - it('update schedule is a fixedCollection that always injects the required schedule.type', () => { - const f = anyField('updSchedule')!; - expect(f).toBeDefined(); - expect(f.type).toBe('fixedCollection'); - const expr = (f.routing?.request?.body as Record).schedule; - expect(expr).toContain('type: "interval"'); - expect(expr).toContain('frequency'); - expect(expr).toContain('unit'); - }); - - it('updWebhookRemove clears the webhook by sending null', () => { - const f = anyField('updWebhookRemove')!; - expect(f).toBeDefined(); - expect(f.type).toBe('boolean'); - expect((f.routing?.request?.body as Record).webhook).toContain('null'); - }); - - it('updChangeDetectionType is an options discriminator (exact/semantic) → change_detection.type', () => { - const f = anyField('updChangeDetectionType')!; - expect(f).toBeDefined(); - expect(f.type).toBe('options'); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'exact', - 'semantic', - ]); - expect(sendOf(f)!.property).toBe('change_detection.type'); - }); - - it('updTargetType is an options discriminator (page/sitemap/extract) → target.type', () => { - const f = anyField('updTargetType')!; - expect(f).toBeDefined(); - expect(f.type).toBe('options'); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'extract', - 'page', - 'sitemap', - ]); - expect(sendOf(f)!.property).toBe('target.type'); - }); - - it.each([ - ['updSitemapMaxUrls', 'target.max_urls'], - ['updExtractMaxDepth', 'target.max_depth'], - ['updExtractMaxPages', 'target.max_pages'], - ['updExtractInstructions', 'target.instructions'], - ])('update parity field %s sends body path %s', (name, prop) => { - const f = anyField(name)!; - expect(f).toBeDefined(); - expect(sendOf(f)!.property).toBe(prop); - }); - - it.each([ - ['updSitemapExclude', 'target.exclude'], - ['updSitemapInclude', 'target.include'], - ])('update parity field %s splits csv into %s', (name, prop) => { - const f = anyField(name)!; - expect(f).toBeDefined(); - expect(sendOf(f)!.property).toBe(prop); - expect(sendOf(f)!.value).toContain('split'); - }); - }); - - // ─── list / changes / runs filters + pagination ────────────────────────────── - describe('list filters', () => { - it.each([ - ['status', ['active', 'failed', 'paused']], - ['target_type', ['extract', 'page', 'sitemap']], - ['change_detection_type', ['exact', 'semantic']], - ])('list %s filter is an options field with the SDK enum', (key, values) => { - const f = getAdditionalField(monitorsDescription, key)!; - expect(f).toBeDefined(); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual(values); - expect((f.routing?.request?.qs as Record)[key]).toBeDefined(); - }); - - it('run-status filters use the run enum queued/running/completed/failed', () => { - const f = anyField('runStatus')!; - expect(f).toBeDefined(); - expect((f.options as { value: string }[]).map((o) => o.value).sort()).toEqual([ - 'completed', - 'failed', - 'queued', - 'running', - ]); - }); - - it('cursor + limit pagination exist and route to qs', () => { - for (const key of ['cursor', 'limit']) { - const f = getAdditionalField(monitorsDescription, key)!; - expect(f).toBeDefined(); - expect((f.routing?.request?.qs as Record)[key]).toBeDefined(); - } - }); - - it('since/until/tag/monitor_id filters route to qs', () => { - for (const key of ['since', 'until', 'tag', 'monitor_id']) { - const f = getAdditionalField(monitorsDescription, key)!; - expect(f).toBeDefined(); - expect((f.routing?.request?.qs as Record)[key]).toBeDefined(); - } - }); - }); -}); diff --git a/tests/node.test.ts b/tests/node.test.ts deleted file mode 100644 index 499ca8a..0000000 --- a/tests/node.test.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { NodeConnectionTypes } from 'n8n-workflow'; -import { ContextDev } from '../nodes/ContextDev/ContextDev.node'; -import nodeJson from '../nodes/ContextDev/ContextDev.node.json'; -import packageJson from '../package.json'; - -describe('ContextDev node', () => { - const node = new ContextDev(); - const desc = node.description; - - // ─── Identity ────────────────────────────────────────────────────────────── - - describe('identity', () => { - it('displayName is Context.dev', () => { - expect(desc.displayName).toBe('Context.dev'); - }); - - it('name is contextDev', () => { - expect(desc.name).toBe('contextDev'); - }); - - it('name is NOT branddev (old name)', () => { - expect(desc.name).not.toBe('branddev'); - }); - - it('version is 1', () => { - expect(desc.version).toBe(1); - }); - - it('group is [transform]', () => { - expect(desc.group).toEqual(['transform']); - }); - }); - - // ─── Defaults ───────────────────────────────────────────────────────────── - - describe('defaults', () => { - it('defaults.name is Context.dev', () => { - expect(desc.defaults.name).toBe('Context.dev'); - }); - }); - - // ─── Inputs / Outputs ───────────────────────────────────────────────────── - - describe('inputs and outputs', () => { - it('has one Main input', () => { - expect(desc.inputs).toEqual([NodeConnectionTypes.Main]); - }); - - it('has one Main output', () => { - expect(desc.outputs).toEqual([NodeConnectionTypes.Main]); - }); - }); - - // ─── subtitle ───────────────────────────────────────────────────────────── - - describe('subtitle', () => { - it('subtitle is defined', () => { - expect(desc.subtitle).toBeDefined(); - }); - - it('subtitle is a non-empty string', () => { - expect(typeof desc.subtitle).toBe('string'); - expect((desc.subtitle as string).length).toBeGreaterThan(0); - }); - - it('subtitle references $parameter["operation"] and $parameter["resource"]', () => { - expect(desc.subtitle).toContain('$parameter["operation"]'); - expect(desc.subtitle).toContain('$parameter["resource"]'); - }); - }); - - // ─── API config ──────────────────────────────────────────────────────────── - - describe('API config', () => { - it('baseURL is https://api.context.dev/v1', () => { - expect(desc.requestDefaults?.baseURL).toBe('https://api.context.dev/v1'); - }); - - it('baseURL does NOT contain brand.dev', () => { - expect(desc.requestDefaults?.baseURL).not.toContain('brand.dev'); - }); - - it('integration_name header is n8n', () => { - const headers = desc.requestDefaults?.headers as Record; - expect(headers?.integration_name).toBe('n8n'); - }); - - it('Accept header is application/json', () => { - const headers = desc.requestDefaults?.headers as Record; - expect(headers?.Accept).toBe('application/json'); - }); - - it('Content-Type header is application/json', () => { - const headers = desc.requestDefaults?.headers as Record; - expect(headers?.['Content-Type']).toBe('application/json'); - }); - }); - - // ─── Credentials ─────────────────────────────────────────────────────────── - - describe('credentials', () => { - it('credential name is contextdevApi', () => { - expect(desc.credentials?.[0].name).toBe('contextdevApi'); - }); - - it('credential name is NOT branddevApi (old name)', () => { - expect(desc.credentials?.[0].name).not.toBe('branddevApi'); - }); - - it('credential is marked required', () => { - expect(desc.credentials?.[0].required).toBe(true); - }); - }); - - // ─── Capabilities ────────────────────────────────────────────────────────── - - describe('capabilities', () => { - it('usableAsTool is true', () => { - expect(desc.usableAsTool).toBe(true); - }); - }); - - // ─── Resource dropdown ───────────────────────────────────────────────────── - - describe('resource dropdown', () => { - const resourceProp = () => desc.properties.find((p) => p.name === 'resource')!; - const resourceValues = () => - (resourceProp().options as { value: string }[]).map((o) => o.value); - - it('resource property has noDataExpression: true', () => { - expect(resourceProp().noDataExpression).toBe(true); - }); - - it('has exactly 6 resources', () => { - expect(resourceValues()).toHaveLength(6); - }); - - it('includes brand', () => expect(resourceValues()).toContain('brand')); - it('includes web', () => expect(resourceValues()).toContain('web')); - it('includes aiDataExtraction', () => expect(resourceValues()).toContain('aiDataExtraction')); - it('includes industry', () => expect(resourceValues()).toContain('industry')); - it('includes utility (new)', () => expect(resourceValues()).toContain('utility')); - it('includes monitors (new)', () => expect(resourceValues()).toContain('monitors')); - - it('does NOT include screenshot (merged into web)', () => { - expect(resourceValues()).not.toContain('screenshot'); - }); - }); - - // ─── Operation dropdown defaults ────────────────────────────────────────── - - describe('operation dropdown defaults point to real operations', () => { - it('each operation dropdown default value exists in its own options', () => { - const operationProps = desc.properties.filter( - (p) => p.name === 'operation' && p.type === 'options', - ); - for (const prop of operationProps) { - const optionValues = (prop.options as { value: string }[]).map((o) => o.value); - expect(optionValues).toContain(prop.default); - } - }); - }); - - // ─── displayOptions.show.resource on every operation dropdown ───────────── - - describe('operation dropdowns are scoped to their resource', () => { - const resourceToOpDefault: Record = { - brand: 'retrieve', - web: 'scrapeMd', - aiDataExtraction: 'aiQuery', - industry: 'retrieveNaics', - utility: 'prefetch', - }; - - for (const [resource, sampleOp] of Object.entries(resourceToOpDefault)) { - it(`${resource} operation dropdown has displayOptions.show.resource: ['${resource}']`, () => { - const operationProp = desc.properties.find( - (p) => - p.name === 'operation' && - p.type === 'options' && - (p.options as { value: string }[]).some((o) => o.value === sampleOp), - ); - expect(operationProp).toBeDefined(); - const showResource = ( - operationProp!.displayOptions?.show as Record - )?.resource; - expect(showResource).toEqual([resource]); - }); - } - }); - - // ─── All 5 resource descriptions are spread in ──────────────────────────── - - describe('all resource descriptions are wired up', () => { - it('brand operations exist in node properties', () => { - const ops = desc.properties - .filter((p) => p.name === 'operation') - .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); - expect(ops).toContain('retrieve'); - }); - - it('web operations exist in node properties', () => { - const ops = desc.properties - .filter((p) => p.name === 'operation') - .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); - expect(ops).toContain('scrapeMd'); - }); - - it('aiDataExtraction operations exist in node properties', () => { - const ops = desc.properties - .filter((p) => p.name === 'operation') - .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); - expect(ops).toContain('aiQuery'); - }); - - it('industry operations exist in node properties', () => { - const ops = desc.properties - .filter((p) => p.name === 'operation') - .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); - expect(ops).toContain('retrieveNaics'); - }); - - it('utility operations exist in node properties', () => { - const ops = desc.properties - .filter((p) => p.name === 'operation') - .flatMap((p) => (p.options as { value: string }[]).map((o) => o.value)); - expect(ops).toContain('prefetch'); - }); - }); - - // ─── .node.json codex ───────────────────────────────────────────────────── - - describe('.node.json codex', () => { - it('codex does not reference brand.dev', () => { - const text = JSON.stringify(nodeJson); - expect(text).not.toContain('brand.dev'); - }); - - it('codex does not reference Brand.dev (capitalized)', () => { - const text = JSON.stringify(nodeJson); - expect(text).not.toContain('Brand.dev'); - }); - - it('primaryDocumentation URL points to context.dev', () => { - const text = JSON.stringify(nodeJson); - expect(text).toContain('context.dev'); - }); - }); - - // ─── package.json manifest ──────────────────────────────────────────────── - - describe('package.json n8n manifest', () => { - it('n8nNodesApiVersion is 1', () => { - expect((packageJson as { n8n: { n8nNodesApiVersion: number } }).n8n.n8nNodesApiVersion).toBe(1); - }); - - it('credentials manifest points to ContextdevApi dist path', () => { - const creds = (packageJson as { n8n: { credentials: string[] } }).n8n.credentials; - expect(creds.some((c: string) => c.includes('ContextdevApi'))).toBe(true); - }); - - it('nodes manifest points to ContextDev dist path', () => { - const nodes = (packageJson as { n8n: { nodes: string[] } }).n8n.nodes; - expect(nodes.some((n: string) => n.includes('ContextDev'))).toBe(true); - }); - - it('credentials manifest does NOT reference Branddev (old path)', () => { - const creds = (packageJson as { n8n: { credentials: string[] } }).n8n.credentials; - expect(creds.some((c: string) => c.includes('Branddev') || c.includes('branddev'))).toBe(false); - }); - - it('nodes manifest does NOT reference Branddev (old path)', () => { - const nodes = (packageJson as { n8n: { nodes: string[] } }).n8n.nodes; - expect(nodes.some((n: string) => n.includes('Branddev') || n.includes('branddev'))).toBe(false); - }); - - it('package name is n8n-nodes-context-dev', () => { - expect(packageJson.name).toBe('n8n-nodes-context-dev'); - }); - }); -}); diff --git a/tests/utility.test.ts b/tests/utility.test.ts deleted file mode 100644 index 8e42f84..0000000 --- a/tests/utility.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { utilityDescription } from '../nodes/ContextDev/resources/utility'; -import { - getOperations, - urlFor, - methodFor, - getTopField, - getAdditionalField, - usesBodyRouting, - showsForOperations, - bodyKeyFor, -} from './helpers'; - -describe('utility resource', () => { - // ─── Existence ───────────────────────────────────────────────────────────── - - it('exists as an array (new resource — not in old node)', () => { - expect(Array.isArray(utilityDescription)).toBe(true); - expect(utilityDescription.length).toBeGreaterThan(0); - }); - - // ─── Operations ──────────────────────────────────────────────────────────── - - describe('operations', () => { - it('has exactly 2 operations', () => { - expect(getOperations(utilityDescription)).toHaveLength(2); - }); - - it('has no duplicate operation values', () => { - const values = getOperations(utilityDescription).map((o) => o.value); - expect(new Set(values).size).toBe(values.length); - }); - - it('every operation has an action defined', () => { - const missing = getOperations(utilityDescription).filter((o) => !o.action); - expect(missing).toHaveLength(0); - }); - - it('operation property has noDataExpression: true', () => { - const op = utilityDescription.find((p) => p.name === 'operation'); - expect(op?.noDataExpression).toBe(true); - }); - }); - - // ─── Routing ─────────────────────────────────────────────────────────────── - - describe('routing', () => { - it('prefetch → POST /brand/prefetch', () => { - expect(methodFor(utilityDescription, 'prefetch')).toBe('POST'); - expect(urlFor(utilityDescription, 'prefetch')).toBe('/brand/prefetch'); - }); - - it('prefetchByEmail → POST /brand/prefetch-by-email', () => { - expect(methodFor(utilityDescription, 'prefetchByEmail')).toBe('POST'); - expect(urlFor(utilityDescription, 'prefetchByEmail')).toBe('/brand/prefetch-by-email'); - }); - }); - - // ─── Required fields ─────────────────────────────────────────────────────── - - describe('required fields', () => { - it('domain exists and is required', () => { - const field = getTopField(utilityDescription, 'domain'); - expect(field).toBeDefined(); - expect(field?.required).toBe(true); - }); - - it('email exists and is required', () => { - const field = getTopField(utilityDescription, 'email'); - expect(field).toBeDefined(); - expect(field?.required).toBe(true); - }); - }); - - // ─── displayOptions scoping ──────────────────────────────────────────────── - - describe('displayOptions scoping', () => { - it('domain shows only for prefetch', () => { - const field = getTopField(utilityDescription, 'domain')!; - expect(showsForOperations(field)).toEqual(['prefetch']); - }); - - it('email shows only for prefetchByEmail', () => { - const field = getTopField(utilityDescription, 'email')!; - expect(showsForOperations(field)).toEqual(['prefetchByEmail']); - }); - }); - - // ─── Field routing ───────────────────────────────────────────────────────── - - describe('field routing — POST body', () => { - it('domain uses body routing', () => { - const field = getTopField(utilityDescription, 'domain')!; - expect(usesBodyRouting(field)).toBe(true); - }); - - it('email uses body routing', () => { - const field = getTopField(utilityDescription, 'email')!; - expect(usesBodyRouting(field)).toBe(true); - }); - }); - - // ─── Field types ─────────────────────────────────────────────────────────── - - describe('field types', () => { - it('domain is type string', () => { - expect(getTopField(utilityDescription, 'domain')?.type).toBe('string'); - }); - - it('email is type string', () => { - expect(getTopField(utilityDescription, 'email')?.type).toBe('string'); - }); - - it('timeoutMS is type number', () => { - expect(getAdditionalField(utilityDescription, 'timeoutMS')?.type).toBe('number'); - }); - }); - - // ─── additionalFields ────────────────────────────────────────────────────── - - describe('additionalFields', () => { - it('contains timeoutMS', () => { - expect(getAdditionalField(utilityDescription, 'timeoutMS')).toBeDefined(); - }); - }); - - // ─── Routing key names ──────────────────────────────────────────────────── - - describe('routing key names match API param names', () => { - it('domain field sends key "domain"', () => { - const field = getTopField(utilityDescription, 'domain')!; - expect(bodyKeyFor(field)).toBe('domain'); - }); - - it('email field sends key "email"', () => { - const field = getTopField(utilityDescription, 'email')!; - expect(bodyKeyFor(field)).toBe('email'); - }); - }); - - // ─── All fields have defaults ────────────────────────────────────────────── - - describe('all fields have a default value', () => { - it('no top-level field is missing default', () => { - const missing = utilityDescription - .filter((p) => p.name !== 'operation') - .filter((p) => p.default === undefined); - expect(missing.map((p) => p.name)).toEqual([]); - }); - }); -}); diff --git a/tests/web.test.ts b/tests/web.test.ts deleted file mode 100644 index 96ebc79..0000000 --- a/tests/web.test.ts +++ /dev/null @@ -1,721 +0,0 @@ -import { webDescription } from '../nodes/ContextDev/resources/web'; -import { - getOperations, - operationExists, - urlFor, - methodFor, - allUrls, - getTopField, - getAllTopFields, - getAdditionalField, - usesQsRouting, - usesBodyRouting, - showsForOperations, - additionalFieldShowsFor, - qsKeyFor, - bodyKeyFor, - arrayFormatFor, - getAdditionalFieldFor, - sendPropertyFor, - sendTypeFor, -} from './helpers'; - -describe('web resource', () => { - // ─── Operations ──────────────────────────────────────────────────────────── - - describe('operations', () => { - it('has exactly 11 operations', () => { - expect(getOperations(webDescription)).toHaveLength(11); - }); - - it('has no duplicate operation values', () => { - const values = getOperations(webDescription).map((o) => o.value); - expect(new Set(values).size).toBe(values.length); - }); - - it('every operation has an action defined (required for usableAsTool)', () => { - const missing = getOperations(webDescription).filter((o) => !o.action); - expect(missing).toHaveLength(0); - }); - - it('operation property has noDataExpression: true', () => { - const op = webDescription.find((p) => p.name === 'operation'); - expect(op?.noDataExpression).toBe(true); - }); - }); - - // ─── Routing — correct URLs and methods ─────────────────────────────────── - - describe('routing — correct URLs and methods', () => { - const cases: [string, string, string][] = [ - ['scrapeMd', 'GET', '/web/scrape/markdown'], - ['scrapeHtml', 'GET', '/web/scrape/html'], - ['scrapeImages', 'GET', '/web/scrape/images'], - ['crawl', 'POST', '/web/crawl'], - ['scrapeSitemap', 'GET', '/web/scrape/sitemap'], - ['search', 'POST', '/web/search'], - ['extract', 'POST', '/web/extract'], - ['screenshot', 'GET', '/web/screenshot'], - ['extractStyleguide', 'GET', '/web/styleguide'], - ['extractFonts', 'GET', '/web/fonts'], - ['extractCompetitors', 'GET', '/web/competitors'], - ]; - - test.each(cases)('%s → %s %s', (op, method, url) => { - expect(methodFor(webDescription, op)).toBe(method); - expect(urlFor(webDescription, op)).toBe(url); - }); - }); - - // ─── Migration — old wrong URLs are gone ────────────────────────────────── - - describe('migration — old wrong URLs do not exist', () => { - const banned = [ - '/web/scrape/md', - '/web/crawl/md', - '/web/sitemap', - '/brand/screenshot', - '/brand/styleguide', - '/brand/fonts', - '/brand/competitors', - ]; - - test.each(banned)('%s is gone', (url) => { - expect(allUrls(webDescription)).not.toContain(url); - }); - - it('scrapeHtml is GET not POST', () => { - expect(methodFor(webDescription, 'scrapeHtml')).toBe('GET'); - }); - - it('scrapeImages is GET not POST', () => { - expect(methodFor(webDescription, 'scrapeImages')).toBe('GET'); - }); - }); - - // ─── New operations ──────────────────────────────────────────────────────── - - describe('new operations exist', () => { - it('extract operation exists', () => { - expect(operationExists(webDescription, 'extract')).toBe(true); - }); - - it('extractCompetitors operation exists', () => { - expect(operationExists(webDescription, 'extractCompetitors')).toBe(true); - }); - }); - - // ─── Required fields ─────────────────────────────────────────────────────── - - describe('required fields exist and are marked required', () => { - it('url is required for GET scrape ops (scrapeMd, scrapeHtml, scrapeImages)', () => { - const getUrlFields = getAllTopFields(webDescription, 'url').filter((f) => { - const ops = showsForOperations(f); - return ops.includes('scrapeMd') && !ops.includes('crawl'); - }); - expect(getUrlFields.some((f) => f.required)).toBe(true); - }); - - it('url is required for POST crawl op', () => { - const postUrlFields = getAllTopFields(webDescription, 'url').filter((f) => - showsForOperations(f).includes('crawl'), - ); - expect(postUrlFields.some((f) => f.required)).toBe(true); - }); - - it('url is required for POST extract op', () => { - const postUrlFields = getAllTopFields(webDescription, 'url').filter((f) => - showsForOperations(f).includes('extract'), - ); - expect(postUrlFields.some((f) => f.required)).toBe(true); - }); - - it('domain is required for scrapeSitemap', () => { - const field = getAllTopFields(webDescription, 'domain').find((f) => - showsForOperations(f).includes('scrapeSitemap'), - ); - expect(field?.required).toBe(true); - }); - - it('query is required for search', () => { - const field = getTopField(webDescription, 'query'); - expect(field?.required).toBe(true); - }); - - it('schema is required for extract', () => { - const field = getTopField(webDescription, 'schema'); - expect(field?.required).toBe(true); - }); - }); - - // ─── displayOptions scoping ──────────────────────────────────────────────── - - describe('displayOptions scoping', () => { - it('GET url field does NOT show for crawl or extract (POST ops)', () => { - const getUrlField = getAllTopFields(webDescription, 'url').find( - (f) => - showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), - ); - expect(getUrlField).toBeDefined(); - expect(showsForOperations(getUrlField!)).not.toContain('crawl'); - expect(showsForOperations(getUrlField!)).not.toContain('extract'); - }); - - it('POST url field does NOT show for scrapeMd or scrapeHtml (GET ops)', () => { - const postUrlField = getAllTopFields(webDescription, 'url').find((f) => - showsForOperations(f).includes('crawl'), - ); - expect(postUrlField).toBeDefined(); - expect(showsForOperations(postUrlField!)).not.toContain('scrapeMd'); - expect(showsForOperations(postUrlField!)).not.toContain('scrapeHtml'); - }); - - it('query shows only for search', () => { - const field = getTopField(webDescription, 'query')!; - expect(showsForOperations(field)).toEqual(['search']); - }); - - it('schema shows only for extract', () => { - const field = getTopField(webDescription, 'schema')!; - expect(showsForOperations(field)).toEqual(['extract']); - }); - - it('domain field covers scrapeSitemap, screenshot, extractStyleguide, extractFonts, extractCompetitors (across all domain fields)', () => { - const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => - showsForOperations(f), - ); - expect(allOps).toContain('scrapeSitemap'); - expect(allOps).toContain('screenshot'); - expect(allOps).toContain('extractStyleguide'); - expect(allOps).toContain('extractFonts'); - expect(allOps).toContain('extractCompetitors'); - }); - - it('domain field does NOT show for scrapeMd, crawl, search, extract', () => { - const allOps = getAllTopFields(webDescription, 'domain').flatMap((f) => - showsForOperations(f), - ); - expect(allOps).not.toContain('scrapeMd'); - expect(allOps).not.toContain('crawl'); - expect(allOps).not.toContain('search'); - expect(allOps).not.toContain('extract'); - }); - }); - - // ─── Field routing ───────────────────────────────────────────────────────── - - describe('field routing — GET params in qs, POST params in body', () => { - it('GET url field uses qs routing', () => { - const getUrlField = getAllTopFields(webDescription, 'url').find( - (f) => - showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), - )!; - expect(usesQsRouting(getUrlField)).toBe(true); - }); - - it('POST url field (crawl/extract) uses body routing', () => { - const postUrlField = getAllTopFields(webDescription, 'url').find((f) => - showsForOperations(f).includes('crawl'), - )!; - expect(usesBodyRouting(postUrlField)).toBe(true); - }); - - it('query field uses body routing', () => { - const field = getTopField(webDescription, 'query')!; - expect(usesBodyRouting(field)).toBe(true); - }); - - it('schema field uses body routing', () => { - const field = getTopField(webDescription, 'schema')!; - expect(usesBodyRouting(field)).toBe(true); - }); - - it('domain field uses qs routing', () => { - const domainField = getAllTopFields(webDescription, 'domain').find((f) => - showsForOperations(f).includes('scrapeSitemap'), - )!; - expect(usesQsRouting(domainField)).toBe(true); - }); - }); - - // ─── Field types ─────────────────────────────────────────────────────────── - - describe('field types', () => { - it('schema is type json', () => { - expect(getTopField(webDescription, 'schema')?.type).toBe('json'); - }); - - it('query is type string', () => { - expect(getTopField(webDescription, 'query')?.type).toBe('string'); - }); - - it('maxPages in additionalFields is type number', () => { - expect(getAdditionalField(webDescription, 'maxPages')?.type).toBe('number'); - }); - - it('maxDepth in additionalFields is type number', () => { - expect(getAdditionalField(webDescription, 'maxDepth')?.type).toBe('number'); - }); - - it('followSubdomains in additionalFields is type boolean', () => { - expect(getAdditionalField(webDescription, 'followSubdomains')?.type).toBe('boolean'); - }); - - it('factCheck in additionalFields is type boolean', () => { - expect(getAdditionalField(webDescription, 'factCheck')?.type).toBe('boolean'); - }); - - it('handleCookiePopup in additionalFields is type boolean', () => { - expect(getAdditionalField(webDescription, 'handleCookiePopup')?.type).toBe('boolean'); - }); - }); - - // ─── additionalFields contents ───────────────────────────────────────────── - - describe('additionalFields', () => { - it('maxPages is scoped to crawl only', () => { - const field = getAdditionalField(webDescription, 'maxPages')!; - expect(additionalFieldShowsFor(field)).toEqual(['crawl']); - }); - - it('maxDepth is scoped to crawl only', () => { - const field = getAdditionalField(webDescription, 'maxDepth')!; - expect(additionalFieldShowsFor(field)).toEqual(['crawl']); - }); - - it('maxLinks is scoped to scrapeSitemap only', () => { - const field = getAdditionalField(webDescription, 'maxLinks')!; - expect(additionalFieldShowsFor(field)).toEqual(['scrapeSitemap']); - }); - - it('factCheck is scoped to extract only', () => { - const field = getAdditionalField(webDescription, 'factCheck')!; - expect(additionalFieldShowsFor(field)).toEqual(['extract']); - }); - - it('instructions is scoped to extract only', () => { - const field = getAdditionalField(webDescription, 'instructions')!; - expect(additionalFieldShowsFor(field)).toEqual(['extract']); - }); - - it('numCompetitors is scoped to extractCompetitors only', () => { - const field = getAdditionalField(webDescription, 'numCompetitors')!; - expect(additionalFieldShowsFor(field)).toEqual(['extractCompetitors']); - }); - - it('handleCookiePopup is scoped to screenshot only', () => { - const field = getAdditionalField(webDescription, 'handleCookiePopup')!; - expect(additionalFieldShowsFor(field)).toEqual(['screenshot']); - }); - - it('fullScreenshot is scoped to screenshot only', () => { - const field = getAdditionalField(webDescription, 'fullScreenshot')!; - expect(additionalFieldShowsFor(field)).toEqual(['screenshot']); - }); - - it('queryFanout is scoped to search only', () => { - const field = getAdditionalField(webDescription, 'queryFanout')!; - expect(additionalFieldShowsFor(field)).toEqual(['search']); - }); - - it('freshness is scoped to search only', () => { - const field = getAdditionalField(webDescription, 'freshness')!; - expect(additionalFieldShowsFor(field)).toEqual(['search']); - }); - - it('directUrl is scoped to screenshot, styleguide, fonts (NOT competitors — SDK has no directUrl there)', () => { - const field = getAdditionalField(webDescription, 'directUrl')!; - const ops = additionalFieldShowsFor(field); - expect(ops.sort()).toEqual(['extractFonts', 'extractStyleguide', 'screenshot']); - expect(ops).not.toContain('extractCompetitors'); - }); - - it('visual-op domain is NOT required and omits empty values (enables directUrl-only)', () => { - const domain = getAllTopFields(webDescription, 'domain').find((f) => - showsForOperations(f).includes('screenshot'), - )!; - expect(domain.required).not.toBe(true); - expect((domain.routing?.request?.qs as Record).domain).toContain('|| undefined'); - }); - }); - - // ─── Routing key names match API param names ────────────────────────────── - - describe('routing key names match API param names', () => { - it('GET url field sends key "url"', () => { - const getUrlField = getAllTopFields(webDescription, 'url').find( - (f) => - showsForOperations(f).includes('scrapeMd') && !showsForOperations(f).includes('crawl'), - )!; - expect(qsKeyFor(getUrlField)).toBe('url'); - }); - - it('POST url field sends key "url"', () => { - const postUrlField = getAllTopFields(webDescription, 'url').find((f) => - showsForOperations(f).includes('crawl'), - )!; - expect(bodyKeyFor(postUrlField)).toBe('url'); - }); - - it('domain field sends key "domain"', () => { - const domainField = getAllTopFields(webDescription, 'domain').find((f) => - showsForOperations(f).includes('scrapeSitemap'), - )!; - expect(qsKeyFor(domainField)).toBe('domain'); - }); - - it('query field sends key "query"', () => { - const field = getTopField(webDescription, 'query')!; - expect(bodyKeyFor(field)).toBe('query'); - }); - - it('schema field sends key "schema"', () => { - const field = getTopField(webDescription, 'schema')!; - expect(bodyKeyFor(field)).toBe('schema'); - }); - }); - - // ─── All fields have defaults ────────────────────────────────────────────── - - describe('all fields have a default value', () => { - it('no top-level field is missing default', () => { - const missing = webDescription - .filter((p) => p.name !== 'operation') - .filter((p) => p.default === undefined); - expect(missing.map((p) => p.name)).toEqual([]); - }); - }); - - // ─── Selector & header params (SDK 1.34) — wire-contract guarantees ───────── - // - // The OpenAPI marks includeSelectors/excludeSelectors as style:deepObject, but the - // official Stainless SDK ignores style/explode and serializes EVERY query param via - // qs.stringify(query, { arrayFormat: 'comma' }). So the real server contract is - // comma-joined: includeSelectors: ['a','b'] -> includeSelectors=a%2Cb - // n8n uses the same qs lib, so arrayFormat:'comma' on the GET fields makes the node - // byte-identical to the SDK. On crawl (POST) the params ride in the JSON body as a - // plain array, so no qs/arrayFormat applies. These tests lock that contract. - - describe('selector & header params (SDK 1.34) — wire-contract guarantees', () => { - describe.each(['includeSelectors', 'excludeSelectors'])('%s — GET ops (qs array)', (name) => { - it('is scoped to scrapeMd + scrapeHtml', () => { - const f = getAdditionalField(webDescription, name); - expect(f).toBeDefined(); - expect(additionalFieldShowsFor(f!).sort()).toEqual(['scrapeHtml', 'scrapeMd']); - }); - - it(`routes to qs key "${name}"`, () => { - expect(qsKeyFor(getAdditionalField(webDescription, name)!)).toBe(name); - }); - - it('sets arrayFormat "comma" (byte-identical to the SDK)', () => { - expect(arrayFormatFor(getAdditionalField(webDescription, name)!)).toBe('comma'); - }); - - it('splits the input into an array before serialization', () => { - const f = getAdditionalField(webDescription, name)!; - const expr = (f.routing!.request!.qs as Record)[name]; - expect(expr).toContain('.split(",")'); - }); - }); - - describe.each(['includeSelectorsPost', 'excludeSelectorsPost'])( - '%s — crawl (JSON body array)', - (name) => { - const apiKey = name.replace('Post', ''); - - it('is scoped to crawl only', () => { - expect(additionalFieldShowsFor(getAdditionalField(webDescription, name)!)).toEqual([ - 'crawl', - ]); - }); - - it(`routes to body key "${apiKey}"`, () => { - expect(bodyKeyFor(getAdditionalField(webDescription, name)!)).toBe(apiKey); - }); - - it('does NOT set arrayFormat (body is JSON, not a query string)', () => { - expect(arrayFormatFor(getAdditionalField(webDescription, name)!)).toBeUndefined(); - }); - }, - ); - - describe('headers — deep-object map', () => { - it('is a fixedCollection scoped to the four GET scrape ops', () => { - const f = getAdditionalField(webDescription, 'headers')!; - expect(f.type).toBe('fixedCollection'); - expect(additionalFieldShowsFor(f).sort()).toEqual([ - 'scrapeHtml', - 'scrapeImages', - 'scrapeMd', - 'scrapeSitemap', - ]); - }); - - it('routes to qs key "headers"', () => { - expect(qsKeyFor(getAdditionalField(webDescription, 'headers')!)).toBe('headers'); - }); - - it('exposes Name and Value sub-fields', () => { - const f = getAdditionalField(webDescription, 'headers')!; - const values = (f.options as Array<{ values: Array<{ name: string }> }>)[0].values; - expect(values.map((v) => v.name)).toEqual(['name', 'value']); - }); - - it('builds an object via Object.fromEntries (-> headers[Key]=Value)', () => { - const f = getAdditionalField(webDescription, 'headers')!; - const expr = (f.routing!.request!.qs as Record).headers; - expect(expr).toContain('Object.fromEntries'); - }); - }); - - describe('useMainContentOnly extended to scrapeHtml (1.34)', () => { - it('GET field shows for scrapeMd + scrapeHtml', () => { - const f = getAdditionalField(webDescription, 'useMainContentOnly')!; - expect(additionalFieldShowsFor(f).sort()).toEqual(['scrapeHtml', 'scrapeMd']); - }); - }); - }); - - // ─── extract gained maxDepth + maxPages (SDK 1.34) ────────────────────────── - // These param names already existed on crawl, so they need operation-scoped - // lookup. Extract's maxPages cap is 50; crawl's is 500 — distinct bounds. - describe('extract crawl-control params (SDK 1.34)', () => { - it('maxDepth is exposed for extract and routes to body', () => { - const f = getAdditionalFieldFor(webDescription, 'maxDepth', 'extract'); - expect(f).toBeDefined(); - expect(bodyKeyFor(f!)).toBe('maxDepth'); - }); - - it('maxPages is exposed for extract, routes to body, capped at 50', () => { - const f = getAdditionalFieldFor(webDescription, 'maxPages', 'extract'); - expect(f).toBeDefined(); - expect(bodyKeyFor(f!)).toBe('maxPages'); - expect((f!.typeOptions as { maxValue?: number }).maxValue).toBe(50); - }); - - it('crawl maxPages keeps its own 500 cap (distinct bound)', () => { - const f = getAdditionalFieldFor(webDescription, 'maxPages', 'crawl'); - expect((f!.typeOptions as { maxValue?: number }).maxValue).toBe(500); - }); - }); - - // ─── nested + flat param additions (SDK sync) ─────────────────────────────── - const subNames = (f?: { options?: unknown }) => - ((f?.options as { name: string }[] | undefined) ?? []).map((o) => o.name).sort(); - - describe('pdf nested collection', () => { - it('GET pdf (scrapeMd/scrapeHtml) is a collection routing qs key "pdf"', () => { - const f = getAdditionalFieldFor(webDescription, 'pdf', 'scrapeMd')!; - expect(f.type).toBe('collection'); - expect(additionalFieldShowsFor(f)).toEqual(['scrapeMd', 'scrapeHtml']); - expect(qsKeyFor(f)).toBe('pdf'); - expect(subNames(f)).toEqual(['end', 'shouldParse', 'start']); - }); - - it('crawl pdf routes to body key "pdf", scoped to crawl only', () => { - const f = getAdditionalFieldFor(webDescription, 'pdfPost', 'crawl')!; - expect(f.type).toBe('collection'); - expect(bodyKeyFor(f)).toBe('pdf'); - expect(additionalFieldShowsFor(f)).toEqual(['crawl']); - expect(subNames(f)).toEqual(['end', 'shouldParse', 'start']); - }); - - it('extract pdf routes to body key "pdf", scoped to extract, with all sub-fields', () => { - const f = getAdditionalFieldFor(webDescription, 'pdf', 'extract')!; - expect(f.type).toBe('collection'); - expect(bodyKeyFor(f)).toBe('pdf'); - expect(additionalFieldShowsFor(f)).toEqual(['extract']); - expect(subNames(f)).toEqual(['end', 'shouldParse', 'start']); - }); - }); - - describe('enrichment nested collection (scrapeImages)', () => { - it('is a collection scoped to scrapeImages routing qs key "enrichment"', () => { - const f = getAdditionalField(webDescription, 'enrichment')!; - expect(f.type).toBe('collection'); - expect(additionalFieldShowsFor(f)).toEqual(['scrapeImages']); - expect(qsKeyFor(f)).toBe('enrichment'); - expect(subNames(f)).toEqual(['classification', 'hostedUrl', 'maxTimePerMs', 'resolution']); - }); - }); - - describe('shortenBase64Images', () => { - it('GET variant (scrapeMd) routes to qs, scoped to scrapeMd only', () => { - const f = getAdditionalFieldFor(webDescription, 'shortenBase64Images', 'scrapeMd')!; - expect(f.type).toBe('boolean'); - expect(qsKeyFor(f)).toBe('shortenBase64Images'); - expect(additionalFieldShowsFor(f)).toEqual(['scrapeMd']); - }); - - it('POST variant (crawl) routes to body, scoped to crawl only', () => { - const f = getAdditionalFieldFor(webDescription, 'shortenBase64ImagesPost', 'crawl')!; - expect(bodyKeyFor(f)).toBe('shortenBase64Images'); - expect(additionalFieldShowsFor(f)).toEqual(['crawl']); - }); - }); - - describe('viewport nested collection (screenshot)', () => { - it('is a collection scoped to screenshot routing qs key "viewport"', () => { - const f = getAdditionalField(webDescription, 'viewport')!; - expect(f.type).toBe('collection'); - expect(additionalFieldShowsFor(f)).toEqual(['screenshot']); - expect(qsKeyFor(f)).toBe('viewport'); - expect(subNames(f)).toEqual(['height', 'width']); - }); - }); - - // ─── SDK 1.41 web params (colorScheme, country, numResults, scrollOffset) ──── - describe('colorScheme (SDK 1.41)', () => { - it('is an options field offering light + dark', () => { - const f = getAdditionalField(webDescription, 'colorScheme')!; - expect(f).toBeDefined(); - expect(f.type).toBe('options'); - const values = (f.options as { value: string }[]).map((o) => o.value); - expect(values).toContain('light'); - expect(values).toContain('dark'); - }); - - it('is scoped to screenshot + extractStyleguide and routes to qs key "colorScheme"', () => { - const f = getAdditionalField(webDescription, 'colorScheme')!; - expect(additionalFieldShowsFor(f).sort()).toEqual(['extractStyleguide', 'screenshot']); - expect(qsKeyFor(f)).toBe('colorScheme'); - }); - - it('defaults to "" (Auto)', () => { - expect(getAdditionalField(webDescription, 'colorScheme')!.default).toBe(''); - }); - }); - - describe('scrollOffset (SDK 1.41)', () => { - it('is a number scoped to screenshot, routing qs key "scrollOffset"', () => { - const f = getAdditionalField(webDescription, 'scrollOffset')!; - expect(f).toBeDefined(); - expect(f.type).toBe('number'); - expect(additionalFieldShowsFor(f)).toEqual(['screenshot']); - expect(qsKeyFor(f)).toBe('scrollOffset'); - }); - - it('bounds match SDK (0..100000, default 0)', () => { - const f = getAdditionalField(webDescription, 'scrollOffset')!; - expect(f.default).toBe(0); - expect((f.typeOptions as { minValue?: number; maxValue?: number }).minValue).toBe(0); - expect((f.typeOptions as { minValue?: number; maxValue?: number }).maxValue).toBe(100000); - }); - }); - - describe('numResults (SDK 1.41)', () => { - it('is a number scoped to search, routing body key "numResults"', () => { - const f = getAdditionalField(webDescription, 'numResults')!; - expect(f).toBeDefined(); - expect(f.type).toBe('number'); - expect(additionalFieldShowsFor(f)).toEqual(['search']); - expect(bodyKeyFor(f)).toBe('numResults'); - }); - - it('bounds match SDK (10..100, default 10)', () => { - const f = getAdditionalField(webDescription, 'numResults')!; - expect(f.default).toBe(10); - expect((f.typeOptions as { minValue?: number; maxValue?: number }).minValue).toBe(10); - expect((f.typeOptions as { minValue?: number; maxValue?: number }).maxValue).toBe(100); - }); - }); - - describe('country (SDK 1.41)', () => { - // exact op×routing matrix from the SDK: qs for GET ops, body for POST ops. - // The two GET scrape ops share one qs field; screenshot/crawl/search each get their own. - const cases: [op: string, fieldName: string, loc: 'qs' | 'body', show: string[]][] = [ - ['scrapeMd', 'country', 'qs', ['scrapeMd', 'scrapeHtml']], - ['scrapeHtml', 'country', 'qs', ['scrapeMd', 'scrapeHtml']], - ['screenshot', 'country', 'qs', ['screenshot']], - ['crawl', 'countryPost', 'body', ['crawl']], - ['search', 'country', 'body', ['search']], - ]; - - it.each(cases)('%s: string field routes country via %s with exact scoping', (op, name, loc, show) => { - const f = getAdditionalFieldFor(webDescription, name, op)!; - expect(f).toBeDefined(); - expect(f.type).toBe('string'); - expect(f.default).toBe(''); - expect(additionalFieldShowsFor(f).sort()).toEqual([...show].sort()); - if (loc === 'qs') expect(qsKeyFor(f)).toBe('country'); - else expect(bodyKeyFor(f)).toBe('country'); - }); - - it('country is NOT offered for extractStyleguide (SDK has no country there)', () => { - expect(getAdditionalFieldFor(webDescription, 'country', 'extractStyleguide')).toBeUndefined(); - expect(getAdditionalFieldFor(webDescription, 'countryPost', 'extractStyleguide')).toBeUndefined(); - }); - }); - - // ─── search markdownOptions sub-fields (capability drop fix) ──────────────── - // markdownEnabled only ever sent markdownOptions.enabled — once a user turned - // scraping on for search results, there was no way to configure it. These - // fields expose the rest of WebSearchParams.MarkdownOptions from the SDK. - - describe('search markdownOptions sub-fields', () => { - const booleanCases: [name: string, apiPath: string][] = [ - ['markdownIncludeFrames', 'markdownOptions.includeFrames'], - ['markdownIncludeImages', 'markdownOptions.includeImages'], - ['markdownIncludeLinks', 'markdownOptions.includeLinks'], - ['markdownUseMainContentOnly', 'markdownOptions.useMainContentOnly'], - ['markdownShortenBase64Images', 'markdownOptions.shortenBase64Images'], - ]; - - it.each(booleanCases)('%s is a boolean scoped to search, sending body via "%s"', (name, apiPath) => { - const f = getAdditionalField(webDescription, name); - expect(f).toBeDefined(); - expect(f!.type).toBe('boolean'); - expect(additionalFieldShowsFor(f!)).toEqual(['search']); - expect(sendTypeFor(f!)).toBe('body'); - expect(sendPropertyFor(f!)).toBe(apiPath); - }); - - const numberCases: [name: string, apiPath: string][] = [ - ['markdownMaxAgeMs', 'markdownOptions.maxAgeMs'], - ['markdownTimeoutMS', 'markdownOptions.timeoutMS'], - ['markdownWaitForMs', 'markdownOptions.waitForMs'], - ]; - - it.each(numberCases)('%s is a number scoped to search, sending body via "%s"', (name, apiPath) => { - const f = getAdditionalField(webDescription, name); - expect(f).toBeDefined(); - expect(f!.type).toBe('number'); - expect(additionalFieldShowsFor(f!)).toEqual(['search']); - expect(sendTypeFor(f!)).toBe('body'); - expect(sendPropertyFor(f!)).toBe(apiPath); - }); - - it('all markdownOptions sub-fields only display when markdownEnabled is true', () => { - const names = [...booleanCases, ...numberCases].map(([name]) => name); - for (const name of names) { - const f = getAdditionalField(webDescription, name)!; - const show = f.displayOptions?.show as Record | undefined; - expect(show?.['/markdownEnabled']).toEqual([true]); - } - }); - - describe('markdownPdf nested collection', () => { - it('is a collection scoped to search, sending body via "markdownOptions.pdf"', () => { - const f = getAdditionalField(webDescription, 'markdownPdf'); - expect(f).toBeDefined(); - expect(f!.type).toBe('collection'); - expect(additionalFieldShowsFor(f!)).toEqual(['search']); - expect(sendTypeFor(f!)).toBe('body'); - expect(sendPropertyFor(f!)).toBe('markdownOptions.pdf'); - }); - - it('exposes start, end, shouldParse sub-fields (matches SDK MarkdownOptions.Pdf)', () => { - const f = getAdditionalField(webDescription, 'markdownPdf')!; - const names = (f.options as { name: string }[]).map((o) => o.name).sort(); - expect(names).toEqual(['end', 'shouldParse', 'start']); - }); - - it('only displays when markdownEnabled is true', () => { - const f = getAdditionalField(webDescription, 'markdownPdf')!; - const show = f.displayOptions?.show as Record | undefined; - expect(show?.['/markdownEnabled']).toEqual([true]); - }); - }); - }); -}); From 5d06039b01230901d3b71256c885e8137521b098 Mon Sep 17 00:00:00 2001 From: Nikhil Mathur <70559550+nikhil-mat@users.noreply.github.com> Date: Thu, 20 Aug 2026 04:45:15 +0530 Subject: [PATCH 10/10] Add LICENSE; refresh README and agent docs package.json declared MIT without shipping the licence file, which the n8n community-node checklist expects. README documents all 53 operations by resource, plus running the live suite. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018eb9URz1xqidX41rxa4GPZ --- LICENSE | 21 +++++ README.md | 260 ++++++++++++++++++++++++------------------------------ agents.md | 162 ++++++++++++---------------------- 3 files changed, 190 insertions(+), 253 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bfbb7ec --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Context.dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 3f1568d..8ef8038 100644 --- a/README.md +++ b/README.md @@ -1,192 +1,158 @@ # n8n-nodes-context-dev -This is an n8n community node that lets you interact with the [Context.dev API](https://context.dev) in your n8n workflows. +n8n community node for the [Context.dev](https://context.dev) API — brand intelligence, web scraping, AI extraction, monitoring, and web databases. -Context.dev provides brand intelligence, web scraping, AI-powered data extraction, industry classification, and utility operations — covering millions of brands worldwide. -[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/sustainable-use-license/) workflow automation platform. -[Installation](#installation) | [Operations](#operations) | [Credentials](#credentials) | [Compatibility](#compatibility) | [Differences from Zapier / Make](#differences-from-zapier--make) | [Migrating from n8n-nodes-branddev](#migrating-from-n8n-nodes-branddev) | [Resources](#resources) +**53 operations** across 9 resources, covering the full Context.dev API surface. ## Installation -Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation. +In n8n, go to **Settings → Community nodes → Install** and enter: -For the n8n desktop app, go to **Settings** > **Community Nodes** and search for `n8n-nodes-context-dev`. +``` +n8n-nodes-context-dev +``` -## Operations +Or install it manually: -The node exposes five resources. Each resource groups related operations. +```sh +npm install n8n-nodes-context-dev +``` -### Brand Intelligence +## Credentials -| Operation | Description | -|---|---| -| **Retrieve by Domain** | Get logos, colors, fonts, company info, social media, and more for any domain | -| **Retrieve Simplified** | Faster response returning only domain, title, colors, logos, and backdrops | -| **Retrieve by Company Name** | Search by company name and retrieve full brand data | -| **Retrieve by Email** | Extract domain from email address and retrieve brand data (free/disposable providers blocked) | -| **Retrieve by Stock Ticker** | Look up a company by stock ticker symbol across 12+ global exchanges | -| **Retrieve by ISIN** | Look up a company by International Securities Identification Number | -| **Identify From Transaction** | Match a merchant name from a bank statement to a brand | +1. Get an API key from [context.dev](https://context.dev). +2. In n8n, add a new **Context.dev API** credential. +3. Paste the key. The credential's built-in test verifies it immediately. -**Notable options:** `Force Language` (28 languages), `Max Speed` (faster response, less data), `Max Age` (cache freshness control). +The key is sent as a bearer token on every request. ---- +## Operations -### Web Scraping +### News -| Operation | Description | +| Operation | Endpoint | |---|---| -| **Scrape to Markdown** | Scrape any URL and get back clean markdown content | -| **Scrape HTML** | Scrape any URL and return the raw HTML | -| **Scrape Images** | Extract all images from a webpage including URLs, inline SVGs, and metadata | -| **Crawl Website** | Crawl multiple pages of a website and return markdown for each | -| **Crawl Sitemap** | Parse a domain sitemap and return all discovered page URLs | -| **Extract Structured Data** | Crawl a website and extract data matching a JSON schema you define | -| **Search** | Run a web search and get structured results with optional inline markdown | -| **Screenshot** | Capture a screenshot with options for page type (homepage, pricing, login, etc.) and full-page mode | -| **Extract Styleguide** | Extract colors, typography, spacing, shadows, and UI components from a website | -| **Extract Fonts** | Extract font families, usage statistics, fallbacks, and element counts | -| **Extract Competitors** | Identify direct competitors for a given brand domain | +| Search company news | `POST /news/search` | -**Notable options:** `URL Regex` (filter crawl/sitemap URLs), `Max Depth` / `Max Pages` (crawl limits), `Stop After (Ms)` (time-budget for crawls), `Fact Check` (ground extraction in page content), `Full Page Screenshot`, `Handle Cookie Popup`, `Query Fanout` (broaden search recall), `Scrape Results to Markdown`. +### Parsing ---- - -### AI Data Extraction - -| Operation | Description | +| Operation | Endpoint | |---|---| -| **AI Query** | Define named data points and extract them from any domain using AI | -| **Extract Products** | Extract a product catalog (name, description, pricing, features) from a brand website | -| **Extract Single Product** | Extract structured product data from a specific product page URL | +| Parse Bytes | `POST /parse` | -**AI Query** accepts typed data points (text, number, boolean, date, URL, list) and lets you target specific pages (homepage, pricing, about us, blog, careers, etc.). +### Web Scraping ---- +| Operation | Endpoint | +|---|---| +| Crawl Sitemap | `GET /web/scrape/sitemap` | +| Crawl Website & Scrape Markdown | `POST /web/crawl` | +| Scrape HTML | `GET /web/scrape/html` | +| Scrape Images | `GET /web/scrape/images` | +| Scrape Markdown | `GET /web/scrape/markdown` | +| Scrape Screenshot | `GET /web/screenshot` | +| Web Search | `POST /web/search` | -### Industry Classification +### Web Extraction -| Operation | Description | +| Operation | Endpoint | +|---|---| +| Classify NAICS industries | `GET /web/naics` | +| Classify SIC industries | `GET /web/sic` | +| Extract a single product from a URL | `POST /brand/ai/product` | +| Extract products from a brand's website | `POST /brand/ai/products` | +| Extract Structured Website Data | `POST /web/extract` | +| Find website competitors | `GET /web/competitors` | +| Query website data using AI | `POST /brand/ai/query` | +| Scrape Fonts | `GET /web/fonts` | +| Scrape Styleguide | `GET /web/styleguide` | + +### People + +| Operation | Endpoint | |---|---| -| **Classify NAICS** | Get 2022 NAICS (North American Industry Classification System) codes for a domain or company name | -| **Classify SIC** | Get SIC (Standard Industrial Classification) codes — supports the original 1987 dataset or the latest SEC dataset | +| Enrich Person | `POST /people/enrich` | +| Retrieve Person | `POST /people/retrieve` | -Both operations accept a domain or company name, return results with confidence scores, and support configurable min/max result counts. +### Brand Intelligence ---- +| Operation | Endpoint | +|---|---| +| Brand Search | `GET /brand/search` | +| Identify brand from transaction data | `GET /brand/transaction_identifier` | +| Retrieve brand data | `POST /brand/retrieve` | +| Retrieve brand data by company name | `GET /brand/retrieve-by-name` | +| Retrieve brand data by domain | `GET /brand/retrieve` | +| Retrieve brand data by email address | `GET /brand/retrieve-by-email` | +| Retrieve brand data by ISIN | `GET /brand/retrieve-by-isin` | +| Retrieve brand data by stock ticker | `GET /brand/retrieve-by-ticker` | +| Retrieve simplified brand data by domain | `GET /brand/retrieve-simplified` | ### Utility -| Operation | Description | +| Operation | Endpoint | |---|---| -| **Prefetch by Domain** | Warm the cache for a domain before retrieval to reduce latency | -| **Prefetch by Email** | Same as Prefetch by Domain but accepts an email address (free/disposable emails rejected) | - ---- +| Prefetch brand data by email | `POST /brand/prefetch-by-email` | +| Prefetch brand data for a domain | `POST /brand/prefetch` | +| Prefetch data | `POST /utility/prefetch` | -## Credentials +### Monitors -To use this node, you need a Context.dev API key: - -1. Sign up for a free account at [Context.dev](https://context.dev) -2. Go to your [dashboard](https://app.context.dev) and copy your API key -3. In n8n, create new credentials: - - Go to **Credentials** > **New** - - Search for "Context.dev API" - - Paste your API key +| Operation | Endpoint | +|---|---| +| Create a monitor | `POST /monitors` | +| Delete a monitor | `DELETE /monitors/{monitor_id}` | +| Get a change | `GET /monitors/changes/{change_id}` | +| Get a monitor | `GET /monitors/{monitor_id}` | +| List changes | `GET /monitors/changes` | +| List changes for a monitor | `GET /monitors/{monitor_id}/changes` | +| List monitor runs | `GET /monitors/{monitor_id}/runs` | +| List monitors | `GET /monitors` | +| List runs | `GET /monitors/runs` | +| Monitor credit usage | `GET /monitors/credit-usage` | +| Monitor limits | `GET /monitors/limits` | +| Retrieve a monitor run | `GET /monitors/{monitor_id}/runs/{run_id}` | +| Rotate a monitor webhook secret | `POST /monitors/{monitor_id}/webhook/rotate-secret` | +| Run a monitor now | `POST /monitors/{monitor_id}/run` | +| Update a monitor | `PATCH /monitors/{monitor_id}` | + +### Batch + +| Operation | Endpoint | +|---|---| +| Cancel a batch | `POST /batch/{batch_id}/cancel` | +| Delete a batch | `DELETE /batch/{batch_id}` | +| Get a batch | `GET /batch/{batch_id}` | +| Get batch results | `GET /batch/{batch_id}/results` | +| List batches | `GET /batch/list` | +| Submit a batch | `POST /batch/submit` | + +## Testing against the live API + +The package ships a conformance suite that exercises the node's operations against the real API and +prints a pass/fail line for each. Requests are assembled from the **built node's own routing**, so a +green run is evidence about the published artifact rather than a parallel client. + +```sh +npm run build +CONTEXT_DEV_API_KEY=your-key npm run test:live +``` -The node will automatically test the credentials by making a test request to the Context.dev API. +Read-only operations run by default; cache-warming and mutating ones are opt-in. See +[`tests/live/README.md`](tests/live/README.md) for flags and for enabling the operations that need +record ids or sample values. ## Compatibility -- n8n version 1.0.0 and above -- Requires `n8n-workflow` as a peer dependency - -## Usage - -### Example: Get Brand Logos and Colors - -1. Add the **Context.dev** node to your workflow -2. Select **Brand Intelligence** as the resource -3. Choose **Retrieve by Domain** -4. Enter a domain (e.g., `stripe.com`) -5. The node returns comprehensive brand data including logos (SVG/PNG), brand colors, company description, and social media links - -### Example: Scrape a Website to Markdown - -1. Select **Web Scraping** as the resource -2. Choose **Scrape to Markdown** -3. Enter a URL (e.g., `https://stripe.com/pricing`) -4. Optionally enable **Main Content Only** to strip navigation and footers - -### Example: Extract Structured Data with AI - -1. Select **AI Data Extraction** as the resource -2. Choose **AI Query** -3. Enter a domain (e.g., `stripe.com`) -4. Add data points — for example: `company_tagline` (text), `has_free_tier` (boolean), `pricing_tiers` (list) -5. Optionally restrict analysis to specific pages such as Homepage or Pricing - -### Example: Crawl a Blog - -1. Select **Web Scraping** as the resource -2. Choose **Crawl Website** -3. Enter the site URL (e.g., `https://stripe.com/blog`) -4. Set a **URL Regex** like `^https://stripe\.com/blog/` to stay on-topic -5. Set **Max Pages** and **Stop After (Ms)** to control cost and time - -### Tips - -- Use **Additional Fields** to customize optional parameters on every operation -- The node supports n8n's **Usable as AI Tool** feature, making it compatible with AI agent workflows -- Combine **Extract Competitors** + **Retrieve by Domain** in a loop to build a competitive intelligence pipeline - -## Migrating from n8n-nodes-branddev - -This package replaces `n8n-nodes-branddev`. The underlying service has migrated from Brand.dev to Context.dev. - -### What changed - -| | Old (`n8n-nodes-branddev`) | New (`n8n-nodes-context-dev`) | -|---|---|---| -| Package name | `n8n-nodes-branddev` | `n8n-nodes-context-dev` | -| Node name | Brand.dev | Context.dev | -| Credential type | Brand.dev API | Context.dev API | -| API base URL | `https://api.brand.dev/v1` | `https://api.context.dev/v1` | -| Resources | Brand, Industry Classification, Screenshot/Styleguide | Brand Intelligence, Web Scraping, AI Data Extraction, Industry Classification, Utility | - -### Migration steps - -1. In n8n, go to **Settings** > **Community Nodes** and install `n8n-nodes-context-dev` -2. Uninstall `n8n-nodes-branddev` -3. Create new **Context.dev API** credentials using your Context.dev API key (available at [app.context.dev](https://app.context.dev)) -4. In each workflow that used the old Brand.dev node, replace it with the new Context.dev node and re-select the equivalent resource and operation -5. Operations that existed in the old node are preserved under the **Brand Intelligence** resource with the same names — no parameter changes are required for those operations - -### Operations that moved resources - -The following operations were previously under **Screenshot / Styleguide** and are now under **Web Scraping**: - -- Take Screenshot → Screenshot -- Extract Styleguide → Extract Styleguide -- Extract Fonts → Extract Fonts - -## Development - -To run the test suite: - -```bash -npm test -``` +Requires n8n with `n8nNodesApiVersion: 1`. The node is declarative — every operation is described +as routing metadata and executed by n8n's HTTP engine, with no custom execute step. ## Resources - [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/) -- [Context.dev API Documentation](https://docs.context.dev) -- [Context.dev API Reference](https://docs.context.dev/api-reference) -- [GitHub Repository](https://github.com/context-dot-dev/n8n-integration) +- [Context.dev API documentation](https://docs.context.dev) +- Support: hello@context.dev ## License diff --git a/agents.md b/agents.md index 3e6f254..b9d8063 100644 --- a/agents.md +++ b/agents.md @@ -1,125 +1,75 @@ -# Agent Context — n8n-nodes-context-dev +# Agent context — n8n-nodes-context-dev -## What this repo is +## Read this first -An n8n community node for the [Context.dev API](https://docs.context.dev). It exposes brand intelligence, web scraping, AI data extraction, industry classification, and utility (cache warming) operations as n8n workflow steps. +`nodes/` and `credentials/` are kept in lockstep with the Context.dev API contract and are +maintained as a set, not file by file. Before changing anything under them, open an issue describing +the mismatch you found — a local edit to one resource file is likely to be replaced wholesale the +next time the node is synced to the API. -The node is **declarative-style** — no `execute()` method. All API calls are described as `INodeProperties[]` arrays with `routing` metadata. n8n's HTTP engine handles the actual requests. +Safe to edit directly: `package.json`, `.github/`, `CHANGELOG.md`, `tests/live/fixtures.json`. ---- +## What the node is -## Directory structure +A declarative n8n community node: no `execute()` method. Every operation is an entry in an +`INodeProperties[]` array with `routing` metadata, and n8n's HTTP engine performs the request. -``` -credentials/ - ContextdevApi.credentials.ts — API key credential (Bearer auth) - contextdev.svg — Credential icon - -nodes/ContextDev/ - ContextDev.node.ts — Main node: imports all resources, defines requestDefaults - ContextDev.node.json — Codex (categories, aliases, docs link) - context-dev-logo.svg — Node icon - resources/ - brand/index.ts — Brand Intelligence (7 ops, all GET /brand/*) - web/ - index.ts — Operation dropdown + barrel import - scraping.ts — URL-based ops: scrapeMd, scrapeHtml, scrapeImages, crawl, scrapeSitemap - brand.ts — Domain-based brand ops: screenshot, styleguide, fonts, competitors - searchExtract.ts — search (POST /web/search) + extract (POST /web/extract) - aiDataExtraction/index.ts — AI Query, Extract Products, Extract Single Product - industry/index.ts — Classify NAICS (GET /web/naics), Classify SIC (GET /web/sic) - utility/index.ts — Prefetch by Domain/Email (POST /brand/prefetch*) - -tests/ - helpers.ts — Shared test utilities (getOperation, urlFor, getAdditionalField, etc.) - brand.test.ts — 40+ assertions for brand resource - web.test.ts — 55+ assertions for web resource (routing, migration, scoping) - aiDataExtraction.test.ts — 30+ assertions for AI resource - industry.test.ts — 25+ assertions including migration URL checks - utility.test.ts — 20+ assertions for utility resource - node.test.ts — Node-level: subtitle, inputs/outputs, manifest, codex - credentials.test.ts — Credential structure and auth header -``` +- **53 operations** across 9 resources +- Base URL `https://api.context.dev/v1`, bearer auth +- One file per resource under `nodes/ContextDev/resources/` ---- +| Resource | Display name | Operations | +|---|---|---| +| `news` | News | 1 | +| `parsing` | Parsing | 1 | +| `webScraping` | Web Scraping | 7 | +| `webExtraction` | Web Extraction | 9 | +| `people` | People | 2 | +| `brandIntelligence` | Brand Intelligence | 9 | +| `utility` | Utility | 3 | +| `monitors` | Monitors | 15 | +| `batch` | Batch | 6 | -## Source of truth - -The **TypeScript SDK** is authoritative for endpoint URLs, HTTP methods, and parameter names: +## Layout ``` -/Users/main/Projects/n8n-contextdev/context-typescript-sdk/src/resources/ - brand.ts industry.ts ai.ts web.ts utility.ts +credentials/ + ContextdevApi.credentials.ts — API key credential + credential test +nodes/ContextDev/ + ContextDev.node.ts — node class: resource dropdown, requestDefaults, spreads + ContextDev.node.json — n8n codex: categories, docs link, search aliases + resources//index.ts — INodeProperties[] for one resource +tests/live/ + run-live.mjs — live conformance runner (drives the built node in dist/) + fixtures.json — per-operation arguments and expected statuses ``` -When in doubt about a param name (camelCase vs snake_case), endpoint path, or whether a field is GET qs vs POST body — read the SDK first. - ---- - -## Key architectural rules +## Routing conventions -### Routing types -- **GET** operations send params via `routing: { request: { qs: { key: '={{ $value }}' } } }` -- **POST** operations send params via `routing: { request: { body: { key: '={{ $value }}' } } }` -- When a shared optional field (e.g. `timeoutMS`) applies to both GET and POST operations, it must be **duplicated** with different `name` values (e.g. `timeoutMS` and `timeoutMSPost`) — one qs, one body. Do not share routing across method types. +- query params → `routing.request.qs`; booleans serialise as `"true"`/`"false"` +- body params → `routing.send.property` with the dotted path +- deep-object query → a `collection` field whose sub-fields are the leaf segments +- path params → no field routing; the operation URL interpolates `$parameter["name"]` +- raw binary bodies → a binary input plus a `sendBinaryBody` preSend hook +- optional fields use omit-empty expressions so an unset field is never sent -### displayOptions scoping -- Every field **must** have `displayOptions.show.resource: ['']` AND `operation: [...]` to scope it correctly -- Inside `additionalFields` collections, inner fields use `displayOptions.show['/operation']` (note the leading slash) -- Multiple `additionalFields` blocks are allowed — each scoped to a different set of operations +## Verifying a change -### n8n lint rules (enforced by `npm run lint`) -- Operation `options` array must be alphabetically sorted by `name` -- `additionalFields` items must be alphabetically sorted by `displayName` -- `action` strings must be sentence case -- Every operation option must have an `action` field (required for `usableAsTool: true`) -- No trailing period on `description` fields in operation options -- Credential classes must have an `icon` property - -### All fields need a `default` -n8n crashes on load if any `INodeProperties` is missing `default`. Every field, including inside `additionalFields`, must have `default`. - ---- - -## Adding a new operation - -1. Find the correct resource file (or create one under `nodes/ContextDev/resources/`) -2. Add the operation to the `options` array — alphabetically by `name` -3. Add required input fields scoped to the new operation -4. Add optional fields inside the `additionalFields` collection — alphabetically by `displayName` -5. Check the SDK for the exact param names and HTTP method -6. Run `npm run lint` and fix any issues -7. Add tests to the corresponding `tests/*.test.ts` file — at minimum: routing URL/method, required field existence, key name sent - -## Adding a new resource - -1. Create `nodes/ContextDev/resources//index.ts` -2. Export a `Description: INodeProperties[]` array -3. Import and spread it in `ContextDev.node.ts` -4. Add the resource option to the Resource dropdown (alphabetical order) -5. Add a `tests/.test.ts` - ---- - -## Running things - -```bash -npm install # install deps -npm test # run 246 Jest tests (no API key needed) -npm run lint # n8n ESLint rules -npm run build # compile TypeScript → dist/ +```sh +npm run lint # n8n eslint rules (must be clean for n8n Cloud verification) +npm run build # tsc → dist/, copies icons and the codex +CONTEXT_DEV_API_KEY=... npm run test:live # live conformance against the real API ``` -Tests are purely structural — they read `INodeProperties[]` arrays and assert on routing, scoping, and field types. No HTTP calls, no mocking, no API key required. - ---- +The live suite assembles its requests from the **built node in `dist/`**, so it tests the artifact +that ships rather than a parallel client. Read-only operations run by default; see +`tests/live/README.md`. -## Common gotchas +## Common traps -- **camelCase params**: the API uses camelCase for most params (`timeoutMS`, `maxAgeMs`, `maxSpeed`, `numCompetitors`, `queryFanout`). Don't snake_case them. -- **`timeoutMS` not `timeout_ms`**: this trips up anyone coming from the Python SDK which uses `timeoutMS` too but looks like it might be snake_case. -- **GET scrape ops are GET not POST**: `scrapeMd`, `scrapeHtml`, `scrapeImages` are all GET with params in query string. The old node had them wrong as POST. -- **NAICS/SIC live under `/web/`**: `GET /web/naics` and `GET /web/sic` — not `/brand/naics`. -- **Screenshot/Styleguide/Fonts/Competitors live under `/web/`**: all moved from `/brand/*` to `/web/*`. -- **`fixedCollection` routing**: `data_to_extract` in AI Query routes via `'={{ $value.datapoint }}'` — the `.datapoint` accesses the named group inside the fixedCollection. -- **`markdownOptions.enabled`**: uses `routing: { send: { type: 'body', property: 'markdownOptions.enabled' } }` — not a nested object literal (which wouldn't evaluate expressions). +- Editing a resource file to add a single operation. Resource files are maintained as a set against + the API contract; a one-off addition here will not survive the next sync. +- Renaming an operation `value` or the credential `name`. These are load-bearing identifiers: + changing one breaks every saved workflow or credential in the wild. +- Adding an assertion that restates the API contract (endpoint URLs, enum values, parameter names). + Those are verified upstream before the node is synced; a copy here only goes stale.