diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md index f5c1f870..763af303 100644 --- a/.cursor/rules/README.md +++ b/.cursor/rules/README.md @@ -1,5 +1,37 @@ -# Cursor (optional) +# Cursor Rules — `@contentstack/delivery-sdk` -**Cursor** users: start at **[AGENTS.md](../../AGENTS.md)**. All conventions live in **`skills/*/SKILL.md`**. +Rules for **contentstack-typescript**: TypeScript **CDA** SDK built on **`@contentstack/core`**. -This folder only points contributors to **`AGENTS.md`** so editor-specific config does not duplicate the canonical docs. +## Rules overview + +| Rule | Role | +|------|------| +| [`dev-workflow.md`](dev-workflow.md) | Branch/PR, build, tests (`unit` / `api` / `browser`), e2e | +| [`typescript.mdc`](typescript.mdc) | TS conventions, `src/`, `config/` | +| [`contentstack-delivery-typescript.mdc`](contentstack-delivery-typescript.mdc) | **stack**, queries, cache, live preview, **core** integration | +| [`testing.mdc`](testing.mdc) | Jest suites, **jest.setup.ts**, env, Playwright | +| [`code-review.mdc`](code-review.mdc) | PR checklist (**always applied**) | + +## Rule application + +| Context | Typical rules | +|---------|----------------| +| **Every session** | `code-review.mdc` | +| **Most files** | `dev-workflow.md` | +| **`src/`** | `typescript.mdc` + `contentstack-delivery-typescript.mdc` | +| **`test/**`** | `testing.mdc` | +| **Rollup / TS config** | `typescript.mdc` | + +## Quick reference + +| File | `alwaysApply` | Globs (summary) | +|------|---------------|-----------------| +| `dev-workflow.md` | no | `**/*.ts`, `**/*.mjs`, `**/*.json` | +| `typescript.mdc` | no | `src/**/*.ts`, `config/**/*.ts`, `jest.config.ts`, `jest.config.browser.ts`, `jest.setup.ts` | +| `contentstack-delivery-typescript.mdc` | no | `src/**/*.ts` | +| `testing.mdc` | no | `test/**/*.ts`, `playwright.config.ts` | +| `code-review.mdc` | **yes** | — | + +## Skills + +- [`skills/README.md`](../../skills/README.md) · [`AGENTS.md`](../../AGENTS.md) diff --git a/.cursor/rules/code-review.mdc b/.cursor/rules/code-review.mdc new file mode 100644 index 00000000..96b501d3 --- /dev/null +++ b/.cursor/rules/code-review.mdc @@ -0,0 +1,27 @@ +--- +description: "PR checklist for @contentstack/delivery-sdk — API, types, core bump, tests" +alwaysApply: true +--- + +# Code review — `@contentstack/delivery-sdk` + +## Public API + +- **Exported** `stack`, **Stack**, query/entry/asset types match **README** and **`.d.ts`** output in **`dist/modern/`**. +- **JSDoc** on **`stack()`** and key public methods when behavior or options change. + +## Compatibility + +- Avoid breaking **StackConfig** or method chains without semver strategy; document migration for breaking changes. + +## Core / deps + +- **`@contentstack/core`** version changes: verify interceptors, errors, and **httpClient** options in **`contentstack.ts`**. + +## Tests + +- **Unit** coverage for new logic; **API** updates when CDA request/response behavior changes; **browser** if bundling or globals affected. + +## Security + +- No hardcoded tokens; no logging secrets in new code. diff --git a/.cursor/rules/contentstack-delivery-typescript.mdc b/.cursor/rules/contentstack-delivery-typescript.mdc new file mode 100644 index 00000000..3273eade --- /dev/null +++ b/.cursor/rules/contentstack-delivery-typescript.mdc @@ -0,0 +1,33 @@ +--- +description: "CDA Delivery SDK — stack, queries, cache, live preview, @contentstack/core" +globs: ["src/**/*.ts"] +alwaysApply: false +--- + +# Contentstack TypeScript Delivery SDK (`src/`) + +## Stack entry + +- **`stack(config: StackConfig)`** in **`src/stack/contentstack.ts`** resolves **region → host** (`getHostforRegion`), merges **live_preview**, builds the Axios stack via **`httpClient`** + **`retryRequestHandler`** / **`retryResponseHandler`** / **`retryResponseErrorHandler`**, and returns **`Stack`**. + +## Features + +- **Queries** — **`src/query/*`**: base query, entry/asset/taxonomy/content-type/global-field queryables; chain methods match CDA query parameters. +- **Cache** — **`src/cache`** + **`Policy`** on **StackConfig**; persistence plugins may be documented as optional packages in JSDoc. +- **Sync** — **`src/sync/synchronization.ts`** for sync token workflows. + +## Live preview + +- **StackConfig.live_preview** — **enable**, **preview_token**, **host**, etc.; keep behavior aligned with tests under **`test/api/live-preview*.spec.ts`**. + +## Plugins + +- **ContentstackPlugin** interceptors should follow existing **preRequest**/**onData** patterns in **Stack** if extending. + +## Core alignment + +- HTTP defaults (**timeout**, retries, headers) must stay consistent with **`@contentstack/core`** capabilities; avoid duplicating retry logic that belongs in **core**. + +## Docs + +- [Content Delivery API](https://www.contentstack.com/docs/developers/apis/content-delivery-api/) diff --git a/.cursor/rules/dev-workflow.md b/.cursor/rules/dev-workflow.md new file mode 100644 index 00000000..95be5bf5 --- /dev/null +++ b/.cursor/rules/dev-workflow.md @@ -0,0 +1,26 @@ +--- +description: "Branches, build, and test matrix for contentstack-typescript" +globs: ["**/*.ts", "**/*.mjs", "**/*.json"] +alwaysApply: false +--- + +# Development workflow — `@contentstack/delivery-sdk` + +## Before a PR + +1. **`npm run build`** — Rollup + type declarations succeed. +2. **`npm run test:unit`** — required baseline. +3. **API tests** — `npm run test:api` when your change affects live CDA behavior; configure **`.env`** per **`test/utils/stack-instance.ts`**. Never commit tokens. +4. **Browser / e2e** — run when changing bundling, globals, or browser-specific code (`npm run test:browser`, `npm run test:e2e` as needed). + +## Dependency on core + +- Bumps to **`@contentstack/core`** may require alignment of **httpClient** options, interceptors, or error types. Verify **`stack/contentstack.ts`** and retry/plugin code after core upgrades. + +## Versioning + +- Update **`package.json` `version`** per semver for user-visible SDK changes. + +## Links + +- [`AGENTS.md`](../../AGENTS.md) · [`skills/contentstack-delivery-typescript/SKILL.md`](../../skills/contentstack-delivery-typescript/SKILL.md) diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc new file mode 100644 index 00000000..ed3f073a --- /dev/null +++ b/.cursor/rules/testing.mdc @@ -0,0 +1,37 @@ +--- +description: "Jest unit/api/browser tests and Playwright e2e for delivery-sdk" +globs: + - "test/**/*.ts" + - "playwright.config.ts" +alwaysApply: false +--- + +# Testing — `@contentstack/delivery-sdk` + +## Jest + +| Suite | Path | Notes | +|-------|------|--------| +| **Unit** | `test/unit/**/*.spec.ts` | Mocked / fast; `npm run test:unit` | +| **API** | `test/api/**/*.spec.ts` | Real stack — **`.env`** via **`test/utils/stack-instance.ts`** | +| **Browser** | `test/browser/**/*.spec.ts` | `jest.config.browser.ts` | + +- **`jest.setup.ts`** — console capture, suppression of **expected** validation error noise; do not weaken checks for real failures. + +## Env (`test/api` and helpers) + +Required for **`stackInstance()`**: + +- **`HOST`**, **`API_KEY`**, **`DELIVERY_TOKEN`**, **`ENVIRONMENT`** + +Optional: + +- **`PREVIEW_TOKEN`**, **`LIVE_PREVIEW_HOST`** + +## E2E + +- **`npm run test:e2e`** — builds browser bundle then **Playwright** (`test/e2e`, `playwright.config.ts`). + +## Hygiene + +- No permanent **`test.only`** in CI paths; long-running API suites may use **`testTimeout`** in Jest config (`maxWorkers: 1` is intentional). diff --git a/.cursor/rules/typescript.mdc b/.cursor/rules/typescript.mdc new file mode 100644 index 00000000..a60d0a07 --- /dev/null +++ b/.cursor/rules/typescript.mdc @@ -0,0 +1,34 @@ +--- +description: "TypeScript conventions for the Delivery SDK src and tooling" +globs: + - "src/**/*.ts" + - "config/**/*.ts" + - "jest.config.ts" + - "jest.config.browser.ts" + - "jest.setup.ts" + - "jest.setup.browser.ts" + - "rollup.config.js" +alwaysApply: false +--- + +# TypeScript — `@contentstack/delivery-sdk` + +## Layout + +- **`src/stack/`** — **`stack()`** factory and **Stack** implementation. +- **`src/query/`**, **`src/entries/`**, **`src/assets/`**, **`src/sync/`**, **`src/cache/`** — feature modules. +- **`src/common/types.ts`** — **`StackConfig`**, **Region**, plugins, cache policies, etc. + +## Style + +- Follow **`.eslintrc.json`** and existing naming (including eslint disables only where already established, e.g. **stack** factory export). + +## Imports + +- **`@contentstack/core`** — **`httpClient`**, retry handlers. +- **`@contentstack/utils`** — re-exported from **`contentstack.ts`** where applicable. +- **`axios`** types for headers where needed. + +## Security + +- Do not log **delivery tokens**, **preview tokens**, or **api keys**; use existing error and debug patterns. diff --git a/.github/workflows/back-merge-pr.yml b/.github/workflows/back-merge-pr.yml deleted file mode 100644 index 0b3646ea..00000000 --- a/.github/workflows/back-merge-pr.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Back-merge main to development - -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -jobs: - open-back-merge-pr: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Open back-merge PR if needed - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - BASE_BRANCH="development" - SOURCE_BRANCH="main" - - git fetch origin "$BASE_BRANCH" "$SOURCE_BRANCH" - - if ! git show-ref --verify --quiet "refs/remotes/origin/$BASE_BRANCH"; then - echo "Base branch '$BASE_BRANCH' does not exist on origin; skipping." - exit 0 - fi - - SOURCE_SHA=$(git rev-parse "origin/$SOURCE_BRANCH") - BASE_SHA=$(git rev-parse "origin/$BASE_BRANCH") - - if [ "$SOURCE_SHA" = "$BASE_SHA" ]; then - echo "$SOURCE_BRANCH and $BASE_BRANCH are at the same commit; nothing to back-merge." - exit 0 - fi - - EXISTING=$(gh pr list --repo "${{ github.repository }}" --base "$BASE_BRANCH" --head "$SOURCE_BRANCH" --state open --json number --jq 'length') - - if [ "$EXISTING" -gt 0 ]; then - echo "An open PR from $SOURCE_BRANCH to $BASE_BRANCH already exists; skipping." - exit 0 - fi - - gh pr create --repo "${{ github.repository }}" --base "$BASE_BRANCH" --head "$SOURCE_BRANCH" --title "chore: back-merge $SOURCE_BRANCH into $BASE_BRANCH" --body "Automated back-merge after changes landed on \\`$SOURCE_BRANCH\\`. Review and merge to keep \\`$BASE_BRANCH\\` in sync." - - echo "Created back-merge PR $SOURCE_BRANCH -> $BASE_BRANCH." diff --git a/.github/workflows/check-branch.yml b/.github/workflows/check-branch.yml new file mode 100644 index 00000000..e46fdcbc --- /dev/null +++ b/.github/workflows/check-branch.yml @@ -0,0 +1,31 @@ +name: 'Check Branch' + +on: + pull_request: + +jobs: + check_branch: + runs-on: ubuntu-latest + steps: + - name: Comment PR + if: github.base_ref == 'main' && github.head_ref != 'staging' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + We regret to inform you that you are currently not able to merge your changes into the main branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the main branch. + - name: Check branch + if: github.base_ref == 'main' && github.head_ref != 'staging' + run: | + echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the main branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the main branch." + exit 1 + - name: Comment PR for staging + if: github.base_ref == 'staging' && github.head_ref != 'development' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + We regret to inform you that you are currently not able to merge your changes into the staging branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the staging branch. + - name: Check branch for staging + if: github.base_ref == 'staging' && github.head_ref != 'development' + run: | + echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the staging branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the staging branch." + exit 1 diff --git a/.github/workflows/check-version-bump.yml b/.github/workflows/check-version-bump.yml deleted file mode 100644 index 60669303..00000000 --- a/.github/workflows/check-version-bump.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Check Version Bump - -on: - pull_request: - -jobs: - version-bump: - name: Version & Changelog bump - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Detect changed files and version bump - id: detect - run: | - if git rev-parse HEAD^2 >/dev/null 2>&1; then - FILES=$(git diff --name-only HEAD^1 HEAD^2) - else - FILES=$(git diff --name-only HEAD~1 HEAD) - fi - VERSION_FILES_CHANGED=false - echo "$FILES" | grep -qx 'package.json' && VERSION_FILES_CHANGED=true - echo "$FILES" | grep -qx 'CHANGELOG.md' && VERSION_FILES_CHANGED=true - echo "version_files_changed=$VERSION_FILES_CHANGED" >> $GITHUB_OUTPUT - # Only lib/, webpack/, dist/, package.json count as release-affecting; .github/ and test/ do not - CODE_CHANGED=false - echo "$FILES" | grep -qE '^lib/|^webpack/|^dist/' && CODE_CHANGED=true - echo "$FILES" | grep -qx 'package.json' && CODE_CHANGED=true - echo "code_changed=$CODE_CHANGED" >> $GITHUB_OUTPUT - - - name: Skip when only test/docs/.github changed - if: steps.detect.outputs.code_changed != 'true' - run: | - echo "No release-affecting files changed (e.g. only test/docs/.github). Skipping version-bump check." - exit 0 - - - name: Fail when version bump was missed - if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_changed != 'true' - run: | - echo "::error::This PR has code changes but no version bump. Please bump the version in package.json and add an entry in CHANGELOG.md." - exit 1 - - - name: Setup Node - if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_changed == 'true' - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - - name: Check version bump - if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_changed == 'true' - run: | - set -e - PKG_VERSION=$(node -p "require('./package.json').version.replace(/^v/, '')") - if [ -z "$PKG_VERSION" ]; then - echo "::error::Could not read version from package.json" - exit 1 - fi - git fetch --tags --force 2>/dev/null || true - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true) - if [ -z "$LATEST_TAG" ]; then - echo "No existing tags found. Skipping version-bump check (first release)." - exit 0 - fi - LATEST_VERSION="${LATEST_TAG#v}" - LATEST_VERSION="${LATEST_VERSION%%-*}" - if [ "$(printf '%s\n' "$LATEST_VERSION" "$PKG_VERSION" | sort -V | tail -1)" != "$PKG_VERSION" ]; then - echo "::error::Version bump required: package.json version ($PKG_VERSION) is not greater than latest tag ($LATEST_TAG). Please bump the version in package.json." - exit 1 - fi - if [ "$PKG_VERSION" = "$LATEST_VERSION" ]; then - echo "::error::Version bump required: package.json version ($PKG_VERSION) equals latest tag ($LATEST_TAG). Please bump the version in package.json." - exit 1 - fi - CHANGELOG_VERSION=$(sed -nE 's/^(## \[v?|### Version: )([0-9]+\.[0-9]+\.[0-9]+).*/\2/p' CHANGELOG.md | head -1) - if [ -z "$CHANGELOG_VERSION" ]; then - echo "::error::Could not find a version entry in CHANGELOG.md (expected '## [v1.0.0](...)' or '### Version: 1.0.0')." - exit 1 - fi - if [ "$CHANGELOG_VERSION" != "$PKG_VERSION" ]; then - echo "::error::CHANGELOG version mismatch: CHANGELOG.md top version ($CHANGELOG_VERSION) does not match package.json version ($PKG_VERSION). Please add or update the CHANGELOG entry for $PKG_VERSION." - exit 1 - fi - echo "Version bump check passed: package.json and CHANGELOG.md are at $PKG_VERSION (latest tag: $LATEST_TAG)." diff --git a/.github/workflows/coverage-check.yml b/.github/workflows/coverage-check.yml index d2c99f65..6cf518be 100644 --- a/.github/workflows/coverage-check.yml +++ b/.github/workflows/coverage-check.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - development + - staging - main jobs: diff --git a/.github/workflows/issues-jira.yml b/.github/workflows/issues-jira.yml new file mode 100644 index 00000000..7bf04694 --- /dev/null +++ b/.github/workflows/issues-jira.yml @@ -0,0 +1,31 @@ +name: Create Jira Ticket for Github Issue + +on: + issues: + types: [opened] + +jobs: + issue-jira: + runs-on: ubuntu-latest + steps: + + - name: Login to Jira + uses: atlassian/gajira-login@master + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Create Jira Issue + id: create_jira + uses: atlassian/gajira-create@master + with: + project: ${{ secrets.JIRA_PROJECT }} + issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} + summary: Github | Issue | ${{ github.event.repository.name }} | ${{ github.event.issue.title }} + description: | + *GitHub Issue:* ${{ github.event.issue.html_url }} + + *Description:* + ${{ github.event.issue.body }} + fields: "${{ secrets.ISSUES_JIRA_FIELDS }}" \ No newline at end of file diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index 79b65575..2307d489 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -5,9 +5,6 @@ on: jobs: security-sca: runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write steps: - uses: actions/checkout@master - name: Run Snyk to check for vulnerabilities diff --git a/.talismanrc b/.talismanrc index cb20436e..692eac28 100644 --- a/.talismanrc +++ b/.talismanrc @@ -64,14 +64,4 @@ fileignoreconfig: checksum: 9185df498914e2966d78d9d216acaaa910d43cd7ac9a5e9a26e7241ac9edc9b5 - filename: test/reporting/generate-unified-report.js checksum: 9e7a4696561b790cb93f3be8406a70ec6fdc90a3f8bbb9739504495690158fe3 -- filename: src/query/term-query.ts - checksum: 1f5b23177460d562076d93cf28b375106b19123a5ab135ffef75f4b2bb332d35 -- filename: test/bundlers/run-with-report.sh - checksum: fedb0c262e3d88ad3537943e828d8ed9412a9f7d78b6406997b3955b29816f20 -- filename: test/utils/assertion-tracker.ts - checksum: f02ce0af5948cd813020367c21da2cd0cd00168eeeb9e8af1858b852ae83e269 -- filename: test/utils/request-capture-plugin.ts - checksum: 596fbbbf4aace2431dc165208a81f1a03c5f1d5268aceda83385debeaba79b97 -- filename: test/reporting/rich-html-reporter.cjs - checksum: 1da275d7d083cc671a3888b1a045a616f79ac1fe023ee64ea34f0f23ddbc3706 version: "1.0" diff --git a/AGENTS.md b/AGENTS.md index 49e81461..a1fe6f5e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,45 +1,59 @@ -# Contentstack TypeScript Delivery SDK – Agent guide +# AGENTS.md — AI / automation context -**Universal entry point** for contributors and AI agents. Detailed conventions live in **`skills/*/SKILL.md`**. +## Project -## What this repo is +| | | +|---|---| +| **Name** | **`@contentstack/delivery-sdk`** (npm) — **Contentstack TypeScript Content Delivery SDK** | +| **Purpose** | TypeScript client for the **Content Delivery API (CDA)**: stacks, entries, assets, queries, sync, live preview, cache. Built on **`@contentstack/core`** (**Axios** HTTP + retry helpers) and **`@contentstack/utils`**. | +| **Repository** | [contentstack/contentstack-typescript](https://github.com/contentstack/contentstack-typescript.git) | -| Field | Detail | -|--------|--------| -| **Name:** | [contentstack-typescript](https://github.com/contentstack/contentstack-typescript) (`@contentstack/delivery-sdk`) | -| **Purpose:** | TypeScript/JavaScript Content Delivery SDK for fetching and working with stack content in Node and browsers. | -| **Out of scope:** | Not the Management API or CLI; use the appropriate Contentstack tools for non-delivery workflows. | - -## Tech stack (at a glance) +## Tech stack | Area | Details | |------|---------| -| Language | TypeScript (`typescript` in `package.json`); Node **≥ 18** | -| Build | Rollup (`rollup -c`), declaration emit (`config/tsconfig.decl-esm.json`) → `dist/modern/` | -| Tests | Jest: `test/unit`, `test/api`, browser config; Playwright for e2e (`test/e2e`); bundler smoke tests under `test/bundlers/` | -| Lint / coverage | No root `lint` script—use `npm run validate:all` and `.github/workflows/coverage-check.yml` for quality gates | -| CI | `.github/workflows/coverage-check.yml`, `check-branch.yml`, `sca-scan.yml`, `policy-scan.yml`, `npm-publish.yml` | - -## Commands (quick reference) - -| Command type | Command | -|--------------|---------| -| Build | `npm run build` | -| Test (common) | `npm run test:unit` / `npm run test:api` / `npm run test:all` | -| Validate | `npm run validate:all` | -| Full CI-style suite | `npm run test:cicd` or `npm run test:cicd:no-browser` (see `package.json`) | - -## Where the documentation lives: skills - -| Skill | Path | What it covers | -|-------|------|----------------| -| **Development workflow** | [`skills/dev-workflow/SKILL.md`](skills/dev-workflow/SKILL.md) | Branches, CI, npm scripts, prerelease | -| **Delivery SDK** | [`skills/contentstack-delivery-typescript/SKILL.md`](skills/contentstack-delivery-typescript/SKILL.md) | Public API, stack client, `@contentstack/core` usage | -| **TypeScript & layout** | [`skills/typescript/SKILL.md`](skills/typescript/SKILL.md) | `src/`, Rollup outputs, modern CJS/ESM | -| **Testing** | [`skills/testing/SKILL.md`](skills/testing/SKILL.md) | Jest, API tests, Playwright, bundler matrix | -| **Build & platform** | [`skills/framework/SKILL.md`](skills/framework/SKILL.md) | Rollup, browser safety, bundler validation | -| **Code review** | [`skills/code-review/SKILL.md`](skills/code-review/SKILL.md) | PR checklist for SDK changes | - -## Using Cursor (optional) - -If you use **Cursor**, [`.cursor/rules/README.md`](.cursor/rules/README.md) only points to **`AGENTS.md`**—same docs as everyone else. +| **Language** | **TypeScript**, **ES modules** (`"type": "module"`) | +| **Runtime** | Node **>= 18** (`package.json` `engines`) | +| **Build** | **Rollup** (`npm run build:rollup`) + **`tsc`** declarations (`config/tsconfig.decl-esm.json`) → **`dist/modern/`** | +| **Tests** | **Jest** + **ts-jest**: **`test/unit`**, **`test/api`**, **`test/browser`**; **Playwright** e2e (`test/e2e`, `npm run test:e2e`) | +| **Lint** | **ESLint** (`.eslintrc.json`) | + +## Source layout + +| Path | Role | +|------|------| +| `src/stack/contentstack.ts` | **`stack(config)`** factory — wires **`httpClient`** from **`@contentstack/core`**, region/host, live preview | +| `src/stack/stack.ts` | **Stack** class | +| `src/query/**` | Queries (entry, asset, taxonomy, content type, …) | +| `src/entries/**`, `src/assets/**`, `src/sync/**`, `src/cache/**` | Domain modules | +| `src/common/**` | Types, utils, errors, pagination | +| `src/index.ts` | Public package exports | +| `test/utils/stack-instance.ts` | **`stackInstance()`** — loads **dotenv**, **`HOST`**, **`API_KEY`**, **`DELIVERY_TOKEN`**, **`ENVIRONMENT`**, optional live-preview vars | + +## Common commands + +```bash +npm install +npm run build +npm run test:unit # jest ./test/unit +npm run test:api # live API — needs .env (see stack-instance) +npm run test:browser +npm run test:e2e # Playwright (builds browser bundle first) +npm run test:all # unit + browser + api +``` + +## Environment variables (API / integration tests) + +Loaded via **`dotenv`** in **`test/utils/stack-instance.ts`**: + +- **`HOST`**, **`API_KEY`**, **`DELIVERY_TOKEN`**, **`ENVIRONMENT`** — stack connection +- Optional: **`PREVIEW_TOKEN`**, **`LIVE_PREVIEW_HOST`** for live preview tests + +Do not commit secrets. + +## Further guidance + +- **Cursor rules:** [`.cursor/rules/README.md`](.cursor/rules/README.md) +- **Skills:** [`skills/README.md`](skills/README.md) + +Product docs: [Content Delivery API](https://www.contentstack.com/docs/developers/apis/content-delivery-api/). diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ed229b..d9ad64b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,50 +1,3 @@ -### Version: 5.5.2 -#### Date: Aug-05-2026 -Fix: Bump `@contentstack/core` to `^1.5.1`: -- Resolve `MODULE_NOT_FOUND` / "expression is too dynamic" build failures in bundlers (Next.js/Turbopack, webpack) caused by a dynamic `require()` in the keep-alive agent setup — now uses statically analyzable `require('http')`/`require('https')` -- Guard keep-alive agent creation for native ESM (where `require` is undefined) to avoid a `ReferenceError` -- Add package `browser` field so `http`/`https` resolve cleanly in browser bundles - -### Version: 5.5.1 -#### Date: Aug-03-2026 -Fix: Bump `@contentstack/core` to `^1.5.0`: -- Retry of transient network-level errors (`ECONNABORTED`, `ETIMEDOUT`, `ECONNRESET`, `EPIPE`, `EAI_AGAIN`) by default when there is no HTTP response -- Distinct classification of request timeouts instead of a generic `UNKNOWN_ERROR` -- Default `httpAgent`/`httpsAgent` keep-alive connection agents in Node environments - -### Version: 5.5.0 -#### Date: Jul-27-2026 -Enhancement: Entry variants support an optional branch name as the second argument to `variants()` on `Entry` and `Entries`. When provided, the branch is sent as the `branch` request header together with `x-cs-variant-uid`. Existing `variants(uid)` and `variants(uids)` calls remain backward compatible. Added unit and API tests for variant + branch requests. - -### Version: 5.4.0 -#### Date: Jul-16-2026 -Enhancement: Removed `locale?` parameter from `Taxonomy.fetch()` and `Term.fetch()` — locale is now set via the chainable `.param('locale', value)` API, consistent with other query modifiers. -- `.param('locale', 'fr-fr').fetch()` is the correct pattern for localized taxonomy/term fetches -- Updated all tests and the `taxonomy-demo.mjs` script to use the new pattern -- No breaking change for calls that did not pass locale to `fetch()` - -### Version: 5.3.0 -#### Date: Jul-16-2026 -Feature: Added Taxonomy Publishing support to the Content Delivery SDK via `stack.taxonomy()`. -- Fetch all published taxonomies: `stack.taxonomy().find()` -- Fetch a single published taxonomy by UID: `stack.taxonomy(uid).fetch(locale?)` -- Fetch all terms for a taxonomy: `stack.taxonomy(uid).term().find()` -- Fetch a single term by UID: `stack.taxonomy(uid).term(uid).fetch(locale?)` -- Fetch all localized versions of a term: `stack.taxonomy(uid).term(uid).locales()` -- Fetch ancestors of a term: `stack.taxonomy(uid).term(uid).ancestors()` -- Fetch descendants of a term: `stack.taxonomy(uid).term(uid).descendants()` -- Locale support on term queries via chainable `locale()` and `includeFallback()` methods on `TermQuery` - -Note: Taxonomy Publishing requires the `taxonomy_publish`. - -### Version: 5.2.2 -#### Date: June-29-2026 -Fix: Upgrade dependencies - -### Version: 5.2.1 -#### Date: May-25-2026 -Fix: Upgrade dependencies - ### Version: 5.2.0 #### Date: Apr-09-2026 Enhancement: `ContentTypeQuery` extends `BaseQuery` so `stack.contentType()` supports `paginate`, `skip`, `limit`, and related query helpers without mutating `_queryParams`; `includeGlobalFieldSchema()` and `find()` stay backward compatible. Expanded unit tests for `ContentTypeQuery`. diff --git a/jest.config.ts b/jest.config.ts index 9b46d6f4..963a9b91 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -38,12 +38,16 @@ export default { includeConsoleLog: true, }, ], - // Rich single-file HTML report with inline per-test HTTP context (cURL, - // SDK method, request/response). Fixed path (the one the GoCD pipelines link to); - // prints the absolute path at run end. [ - "./test/reporting/rich-html-reporter.cjs", - { outputPath: "reports/contentstack-delivery/html/index.html" }, + "jest-html-reporters", + { + publicPath: "./reports/contentstack-delivery/html", + filename: "index.html", + expand: true, + // Enable console log capture in reports + enableMergeData: true, + dataMergeLevel: 2, + }, ], [ "jest-junit", diff --git a/jest.setup.ts b/jest.setup.ts index 37d61980..1c708f2a 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -6,15 +6,6 @@ */ import * as fs from 'fs'; import * as path from 'path'; -import { - getLastCapturedRequest, - clearCapturedRequests, -} from './test/utils/request-capture-plugin'; -import { - installAssertionTracker, - clearAssertions, - getAssertions, -} from './test/utils/assertion-tracker'; // Store captured console logs interface ConsoleLog { @@ -46,7 +37,7 @@ const originalConsole = { const expectedErrors = [ 'Invalid key:', // From query.search() validation 'Invalid value (expected string or number):', // From query.equalTo() validation - 'Invalid argument. Provide a string or an array', // From entry/entries.includeReference() validation (ErrorMessages.INVALID_ARGUMENT_STRING_OR_ARRAY) + 'Argument should be a String or an Array.', // From entry/entries.includeReference() validation 'Invalid fieldUid:', // From asset query validation ]; @@ -85,47 +76,6 @@ console.error = captureConsole('error'); console.info = captureConsole('info'); console.debug = captureConsole('debug'); -// --------------------------------------------------------------------------- -// Rich per-test HTTP context (cURL / SDK method / request+response / status). -// Active only when ENABLE_HTTP_CAPTURE=true (the request-capture plugin is -// attached to the stack instance under the same flag). Each test's last -// captured HTTP call is appended to a JSONL sidecar that the custom -// rich-html-reporter reads at run-end to build the single-file HTML report. -// --------------------------------------------------------------------------- -const HTTP_CAPTURE_ENABLED = process.env.ENABLE_HTTP_CAPTURE === 'true'; -const CAPTURES_FILE = path.resolve(__dirname, 'test-results', 'http-captures.jsonl'); - -if (HTTP_CAPTURE_ENABLED) { - beforeEach(() => { - // Install inside beforeEach so it runs AFTER the spec's `import { expect } from - // '@jest/globals'` has resolved the shared module object (idempotent via a guard). - // Records every assertion (expected/actual/pass) without changing any test. - installAssertionTracker(); - clearCapturedRequests(); - clearAssertions(); - }); - - afterEach(() => { - try { - const cap = getLastCapturedRequest(); - const assertions = getAssertions(); - if (!cap && assertions.length === 0) return; - const state: any = (expect as any).getState(); - const rec = { - testPath: state.testPath, - testName: state.currentTestName, - capture: cap || null, - assertions, - }; - const dir = path.dirname(CAPTURES_FILE); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - fs.appendFileSync(CAPTURES_FILE, JSON.stringify(rec) + '\n'); - } catch { - // never let reporting break a test - } - }); -} - // After all tests complete, write logs to file afterAll(() => { const logsPath = path.resolve(__dirname, 'test-results', 'console-logs.json'); diff --git a/package-lock.json b/package-lock.json index 895e22fc..d310abef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,25 @@ { "name": "@contentstack/delivery-sdk", - "version": "5.5.2", + "version": "5.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/delivery-sdk", - "version": "5.5.2", + "version": "5.2.0", "license": "MIT", "dependencies": { - "@contentstack/core": "^1.5.1", - "@contentstack/utils": "^1.9.1", - "axios": "^1.18.1", + "@contentstack/core": "^1.3.11", + "@contentstack/utils": "^1.8.0", + "axios": "^1.15.0", "humps": "^2.0.1" }, "devDependencies": { - "@playwright/test": "^1.61.1", + "@playwright/test": "^1.58.2", "@rollup/plugin-commonjs": "^27.0.0", "@rollup/plugin-node-resolve": "^15.3.1", "@rollup/plugin-replace": "^5.0.7", - "@slack/bolt": "^4.7.3", + "@slack/bolt": "^4.6.0", "@types/humps": "^2.0.6", "@types/jest": "^29.5.14", "@types/node-localstorage": "^1.3.3", @@ -44,13 +44,13 @@ } }, "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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -59,9 +59,9 @@ } }, "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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -69,21 +69,21 @@ } }, "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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -100,14 +100,14 @@ } }, "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==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -117,14 +117,14 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -134,9 +134,9 @@ } }, "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==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", "engines": { @@ -144,29 +144,29 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -176,9 +176,9 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -186,9 +186,9 @@ } }, "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -196,9 +196,9 @@ } }, "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==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -206,9 +206,9 @@ } }, "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -216,27 +216,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "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==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -301,13 +301,13 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -343,13 +343,13 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -469,13 +469,13 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -485,33 +485,33 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@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", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -519,14 +519,14 @@ } }, "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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -540,15 +540,15 @@ "license": "MIT" }, "node_modules/@contentstack/core": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.5.1.tgz", - "integrity": "sha512-sz07yKU+NI3zo8WDtYfJO23cqhglChxbE+CQt5smpTLCHv3hU30vX1nFGLI9tPAuBOCg3pJJDeDohgUNesJGeQ==", + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.3.13.tgz", + "integrity": "sha512-bsCwB7nPr7Ti3vaz3B6AAHUwxC7f16kiwIdzNqbMrtamvbwTCOJpPUXuXFpkWWHmGuBqWZz6WyJzr6ucOf4MDQ==", "license": "MIT", "dependencies": { - "axios": "^1.18.1", + "axios": "^1.15.0", "axios-mock-adapter": "^2.1.0", "lodash": "^4.18.1", - "qs": "6.15.2", + "qs": "6.15.1", "tslib": "^2.8.1" } }, @@ -595,10 +595,82 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -613,6 +685,384 @@ "node": ">=18" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -879,9 +1329,9 @@ } }, "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==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -942,9 +1392,9 @@ } }, "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -1134,13 +1584,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", - "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.61.1" + "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -1223,9 +1673,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", - "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1273,17 +1723,17 @@ } }, "node_modules/@slack/bolt": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.7.3.tgz", - "integrity": "sha512-bODs8q/yNDWUPoxmQhFrRqLMA5vhB/PDizYWqb6CkQhLWEUo5JFtfJcmeU4ElGl6qSt++OKjSYNa4MPc77CleQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.7.0.tgz", + "integrity": "sha512-Xpf+gKegNvkHpft1z4YiuqZdciJ3tUp1bIRQxylW30Ovf+hzjb0M1zTHVtJsRw9jsjPxHTPoyanEXVvG6qVE1g==", "dev": true, "license": "MIT", "dependencies": { "@slack/logger": "^4.0.1", "@slack/oauth": "^3.0.5", - "@slack/socket-mode": "^2.0.7", - "@slack/types": "^2.21.1", - "@slack/web-api": "^7.16.0", + "@slack/socket-mode": "^2.0.6", + "@slack/types": "^2.20.1", + "@slack/web-api": "^7.15.0", "axios": "^1.12.0", "express": "^5.0.0", "path-to-regexp": "^8.1.0", @@ -1331,9 +1781,9 @@ } }, "node_modules/@slack/socket-mode": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.7.tgz", - "integrity": "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.6.tgz", + "integrity": "sha512-Aj5RO3MoYVJ+b2tUjHUXuA3tiIaCUMOf1Ss5tPiz29XYVUi6qNac2A8ulcU1pUPERpXVHTmT1XW6HzQIO74daQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1350,9 +1800,9 @@ } }, "node_modules/@slack/types": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.21.1.tgz", - "integrity": "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.20.1.tgz", + "integrity": "sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A==", "dev": true, "license": "MIT", "engines": { @@ -1361,17 +1811,17 @@ } }, "node_modules/@slack/web-api": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.17.0.tgz", - "integrity": "sha512-jejr34a8B4L5AS713wOAx1LAqNkW16HVMDEa6sYBvFDc/llUBl8hXaiI4BwF+Al+Sug19Vn2O7iokTVIhVvZ1Q==", + "version": "7.15.1", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.15.1.tgz", + "integrity": "sha512-y+TAF7TszcmFzbVtBkFqAdBwKSoD+8shkNxhp4WIfFwXmCKdFje9WD6evROApPa2FTy1v1uc9yBaJs3609PPgg==", "dev": true, "license": "MIT", "dependencies": { "@slack/logger": "^4.0.1", - "@slack/types": "^2.21.0", + "@slack/types": "^2.20.1", "@types/node": ">=18", "@types/retry": "0.12.0", - "axios": "^1.16.0", + "axios": "^1.15.0", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", @@ -1386,9 +1836,9 @@ } }, "node_modules/@tootallnate/once": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", - "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, "license": "MIT", "engines": { @@ -1492,9 +1942,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "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" }, @@ -1619,13 +2069,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", - "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/node-localstorage": { @@ -1639,9 +2089,9 @@ } }, "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "dev": true, "license": "MIT", "peer": true @@ -1755,9 +2205,9 @@ } }, "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -1795,6 +2245,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "4" @@ -1906,14 +2357,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", - "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.16.0", + "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -2038,9 +2488,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.38", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", - "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2064,21 +2514,21 @@ } }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^1.0.5", "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" @@ -2088,24 +2538,10 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -2126,9 +2562,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "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": [ { @@ -2146,10 +2582,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "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": { @@ -2256,9 +2692,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", "dev": true, "funding": [ { @@ -2629,6 +3065,7 @@ "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" @@ -2799,9 +3236,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.378", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz", - "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==", + "version": "1.5.343", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.343.tgz", + "integrity": "sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==", "dev": true, "license": "ISC" }, @@ -2884,9 +3321,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "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", "dependencies": { "es-errors": "^1.3.0" @@ -2911,9 +3348,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2925,32 +3362,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -3271,16 +3708,16 @@ } }, "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" @@ -3554,9 +3991,9 @@ } }, "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3685,6 +4122,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -3833,13 +4271,13 @@ } }, "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==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.3" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4207,9 +4645,9 @@ } }, "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==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4672,9 +5110,9 @@ } }, "node_modules/jest-runtime/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==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4750,9 +5188,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5030,9 +5468,9 @@ } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5204,9 +5642,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5402,6 +5840,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/natural-compare": { @@ -5436,14 +5875,11 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.49", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.49.tgz", - "integrity": "sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -5469,9 +5905,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.24", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", - "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "dev": true, "license": "MIT" }, @@ -5835,13 +6271,13 @@ } }, "node_modules/playwright": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", - "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.1" + "playwright-core": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -5854,9 +6290,9 @@ } }, "node_modules/playwright-core": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", - "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6001,9 +6437,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -6315,14 +6751,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -6690,9 +7126,9 @@ } }, "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==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6805,9 +7241,9 @@ } }, "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==", + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6817,7 +7253,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.8.0", + "semver": "^7.7.4", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -6858,9 +7294,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -6967,36 +7403,18 @@ } }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^2.0.0", + "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/typescript": { @@ -7028,9 +7446,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, @@ -7119,7 +7537,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, "license": "MIT", "bin": { @@ -7389,9 +7806,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, "license": "MIT", "engines": { @@ -7462,9 +7879,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 05ab44d2..19734714 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/delivery-sdk", - "version": "5.5.2", + "version": "5.2.0", "type": "module", "license": "MIT", "engines": { @@ -26,11 +26,11 @@ "prepare": "npm run build", "test": "jest ./test/unit", "test:unit": "jest ./test/unit", - "test:api": "ENABLE_HTTP_CAPTURE=true jest ./test/api", + "test:api": "jest ./test/api", "test:browser": "jest --config jest.config.browser.ts", "test:e2e": "node test/e2e/build-browser-bundle.js && playwright test", "test:e2e:ui": "node test/e2e/build-browser-bundle.js && playwright test --ui", - "test:api:report": "ENABLE_HTTP_CAPTURE=true jest ./test/api --json --outputFile=test-results/jest-results.json", + "test:api:report": "jest ./test/api --json --outputFile=test-results/jest-results.json", "test:bundlers:report": "cd test/bundlers && ./run-with-report.sh", "test:cicd": "mkdir -p test-results && npm run test:api:report && npm run test:bundlers:report && npm run test:e2e && node test/reporting/generate-unified-report.js", "test:cicd:no-browser": "mkdir -p test-results && npm run test:api:report && npm run test:bundlers:report && node test/reporting/generate-unified-report.js", @@ -47,9 +47,9 @@ "prerelease": "npm run test:all && npm run validate:all" }, "dependencies": { - "@contentstack/core": "^1.5.1", - "@contentstack/utils": "^1.9.1", - "axios": "^1.18.1", + "@contentstack/core": "^1.3.11", + "@contentstack/utils": "^1.8.0", + "axios": "^1.15.0", "humps": "^2.0.1" }, "files": [ @@ -61,8 +61,8 @@ "follow-redirects": "^1.16.0" }, "devDependencies": { - "@playwright/test": "^1.61.1", - "@slack/bolt": "^4.7.3", + "@playwright/test": "^1.58.2", + "@slack/bolt": "^4.6.0", "@types/humps": "^2.0.6", "@types/jest": "^29.5.14", "@types/node-localstorage": "^1.3.3", diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..7e2aeb35 --- /dev/null +++ b/skills/README.md @@ -0,0 +1,10 @@ +# Project skills — `@contentstack/delivery-sdk` + +| Skill | When to use | +|-------|-------------| +| [`code-review/`](code-review/SKILL.md) | PR review, semver, core dependency bumps | +| [`testing/`](testing/SKILL.md) | Unit vs API vs browser vs Playwright | +| [`contentstack-delivery-typescript/`](contentstack-delivery-typescript/SKILL.md) | **stack**, Stack class, queries, sync, cache | +| [`framework/`](framework/SKILL.md) | **@contentstack/core** HTTP + retries on the stack | + +**Overview:** [`AGENTS.md`](../AGENTS.md) · **Rules:** [`.cursor/rules/README.md`](../.cursor/rules/README.md) diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md index d48c8529..2cda34c8 100644 --- a/skills/code-review/SKILL.md +++ b/skills/code-review/SKILL.md @@ -1,27 +1,19 @@ --- name: code-review -description: Use when reviewing PRs for the TypeScript Delivery SDK—API, tests, bundler impact, semver. +description: PR review for @contentstack/delivery-sdk — public API, StackConfig, core alignment, tests. --- -# Code review – contentstack-typescript +# Code review — `@contentstack/delivery-sdk` -## When to use +## Checklist -- Reviewing SDK features, fixes, or dependency upgrades -- Assessing risk of a change to browser/Node consumers +- [ ] **API:** New or changed **`stack()`** / **Stack** / query methods documented; exports updated in **`src/index.ts`**. +- [ ] **Types:** **StackConfig** and public interfaces remain consistent with **`dist/modern/*.d.ts`** after build. +- [ ] **@contentstack/core:** Version or API changes validated in **`src/stack/contentstack.ts`** (interceptors, **httpClient** options). +- [ ] **Tests:** **`test:unit`** passes; add/extend **`test/api`** when integration behavior changes; browser/e2e if relevant. +- [ ] **Secrets:** No tokens in repo; **stack-instance** env vars only for local CI secrets store. -## Instructions +## References -### Checklist - -- **Semver**: Public API or default behavior change flagged for major/minor/patch appropriately. -- **Core/utils**: Coordinated version bumps for `@contentstack/core` and `@contentstack/utils` when needed. -- **Tests**: Unit + relevant API/browser/bundler coverage for the change. -- **Build**: `npm run build` succeeds; consider `npm run validate:all` for packaging-sensitive edits. -- **Docs**: README or type docs updated for user-visible changes. - -### Severity hints - -- **Blocker**: Broken `exports`, failing CI, or security issues in dependencies. -- **Major**: Missing tests for cross-bundler or browser regressions. -- **Minor**: Internal refactors with full green matrix. +- `.cursor/rules/code-review.mdc` +- `.cursor/rules/dev-workflow.md` diff --git a/skills/contentstack-delivery-typescript/SKILL.md b/skills/contentstack-delivery-typescript/SKILL.md index 1ce2fb09..03ad66cf 100644 --- a/skills/contentstack-delivery-typescript/SKILL.md +++ b/skills/contentstack-delivery-typescript/SKILL.md @@ -1,26 +1,35 @@ --- name: contentstack-delivery-typescript -description: Use for the public Delivery SDK API, stack initialization, and integration with @contentstack/core and utils. +description: @contentstack/delivery-sdk — TypeScript CDA client, stack factory, queries, sync, cache, live preview. --- -# Contentstack Delivery SDK – contentstack-typescript +# Contentstack TypeScript Delivery SDK skill -## When to use +## Entry -- Changing how consumers initialize the SDK or call stack APIs -- Updating dependencies on `@contentstack/core` or `@contentstack/utils` +- **`contentstack.stack(config)`** — **`src/stack/contentstack.ts`**: merges **StackConfig**, resolves **Region** → host, attaches **@contentstack/core** **`httpClient`** with retry handlers, returns **`Stack`**. -## Instructions +## Structure -### Package identity +- **`Stack`** — **`src/stack/stack.ts`**: content types, entries, assets, sync, taxonomy helpers. +- **Queries** — **`src/query/`**: **BaseQuery**, **Query**, **AssetQuery**, **TaxonomyQuery**, **ContentTypeQuery**, **GlobalFieldQuery**, **EntryQueryable**, etc. +- **Sync** — **`src/sync/`** +- **Cache** — **`src/cache/`** + **Policy** enum in types. -- Published as **`@contentstack/delivery-sdk`**; entry is built into **`dist/modern/`** with dual CJS/ESM typings (`package.json` `exports`). +## Extending -### Design constraints +- Add query methods on the appropriate class; keep param names aligned with **CDA** query docs. +- Prefer delegating transport concerns to **core** rather than duplicating Axios logic. -- Preserve backward compatibility for public imports and options unless shipping a **semver major**. -- HTTP and low-level client behavior often delegate to **`@contentstack/core`**—avoid duplicating retry or error logic; extend in one place when possible. +## Consumer packages -### Documentation +- **`@contentstack/core`** — HTTP + retries +- **`@contentstack/utils`** — utilities; re-exported where documented. -- User-facing behavior belongs in **`README.md`** and official Contentstack docs; keep code samples accurate when changing APIs. +## Docs + +- [Content Delivery API](https://www.contentstack.com/docs/developers/apis/content-delivery-api/) + +## Rule shortcut + +- `.cursor/rules/contentstack-delivery-typescript.mdc` diff --git a/skills/dev-workflow/SKILL.md b/skills/dev-workflow/SKILL.md deleted file mode 100644 index 7fc418b1..00000000 --- a/skills/dev-workflow/SKILL.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: dev-workflow -description: Use for branches, CI, npm scripts, and release flow in contentstack-typescript. ---- - -# Development workflow – contentstack-typescript - -## When to use - -- Choosing which test target to run locally -- Aligning a PR with GitHub Actions (coverage, branch checks, publish) - -## Instructions - -### Branches - -- Default branch is **`main`**; feature work typically merges via PR with required checks. - -### Key commands - -- `npm run build` — produce `dist/modern/` artifacts. -- `npm run test:unit` — fast unit tests. -- `npm run test:api` — API-level Jest tests (may need env; see repo docs). -- `npm run test:all` — unit + browser + API (heavy). -- `npm run validate:all` — browser-safe + bundler validation before release-style work. -- `npm run prerelease` — `test:all` + `validate:all` per `package.json`. - -### CI - -- Workflows under `.github/workflows/` include coverage checks and policy/SCA—keep local runs close to what CI executes for risky changes. diff --git a/skills/framework/SKILL.md b/skills/framework/SKILL.md index 72f620d2..a0015d79 100644 --- a/skills/framework/SKILL.md +++ b/skills/framework/SKILL.md @@ -1,27 +1,23 @@ --- name: framework -description: Use for Rollup build, browser-safe validation, and bundler compatibility in contentstack-typescript. +description: HTTP and retry integration — @contentstack/core with httpClient on the Delivery SDK stack. --- -# Build & platform – contentstack-typescript +# Framework skill — `@contentstack/core` + Delivery SDK -## When to use +## Integration point -- Changing Rollup plugins, bundle splits, or `dist/` layout -- Debugging issues specific to browser, Next.js, Vite, or other bundlers +- **`src/stack/contentstack.ts`** imports **`httpClient`**, **`retryRequestHandler`**, **`retryResponseErrorHandler`**, **`retryResponseHandler`** from **`@contentstack/core`** and composes them with stack-specific **headers**, **live_preview**, and **cache**-related request handling (**`handleRequest`**). -## Instructions +## When to change -### Rollup +- **Retry behavior** shared across Contentstack TS clients → prefer **`@contentstack/core`** (**contentstack-js-core** repo) if appropriate; otherwise document SDK-only overrides here. +- **Base URL / region** — **`getHostforRegion`** and **StackConfig.host** in **`src/common/utils.ts`** (verify imports from current **`contentstack.ts`**). -- Production build: `npm run build:rollup` (high Node memory options may apply—see `package.json`). -- Declaration files: `npm run build:types` (`tsc` with `config/tsconfig.decl-esm.json`). +## Testing -### Browser and bundlers +- **Unit** — mock HTTP layers; **API** — full stack via **`stackInstance()`**. -- `npm run validate:browser` checks browser-safe assumptions. -- `test/bundlers/` validates multiple toolchains—run `./validate-all.sh` or `npm run validate:bundlers` after dependency or export map changes. +## Rule shortcut -### Outputs - -- Consumers import from **`@contentstack/delivery-sdk`**—verify both `import` and `require` paths after changing `exports`. +- `.cursor/rules/contentstack-delivery-typescript.mdc` diff --git a/skills/testing/SKILL.md b/skills/testing/SKILL.md index 4def5a7a..38bbb188 100644 --- a/skills/testing/SKILL.md +++ b/skills/testing/SKILL.md @@ -1,25 +1,34 @@ --- name: testing -description: Use for Jest, Playwright e2e, API tests, and bundler smoke tests in contentstack-typescript. +description: Jest unit/api/browser and Playwright e2e for @contentstack/delivery-sdk. --- -# Testing – contentstack-typescript +# Testing — `@contentstack/delivery-sdk` -## When to use +## Commands -- Adding or fixing tests under `test/unit`, `test/api`, or browser/e2e flows -- Investigating failures in `test/bundlers` or Playwright +| Goal | Command | +|------|---------| +| Unit | `npm run test:unit` | +| API (live stack) | `npm run test:api` | +| Browser | `npm run test:browser` | +| All three | `npm run test:all` | +| E2E | `npm run test:e2e` | +| CI-style matrix | `npm run test:cicd` (includes reports + browser tests) | -## Instructions +## Environment -### Layers +See **`test/utils/stack-instance.ts`**: -- **Unit**: `npm run test:unit` → `jest ./test/unit`. -- **API**: `npm run test:api` — may require stack credentials via env files (see project docs and `.gitignore`). -- **Browser**: `npm run test:browser` uses a dedicated Jest config. -- **E2E**: `npm run test:e2e` builds a browser bundle then runs Playwright—slower; run before large release changes. -- **Bundlers**: `test/bundlers/` exercises webpack/vite/rollup/next/esbuild—run `npm run validate:bundlers` when changing packaging. +- **Required:** `HOST`, `API_KEY`, `DELIVERY_TOKEN`, `ENVIRONMENT` +- **Optional:** `PREVIEW_TOKEN`, `LIVE_PREVIEW_HOST` -### Hygiene +Use a **`.env`** at repo root for local API runs. -- Do not commit secrets or live tokens; use fixtures or CI secrets only where documented. +## Setup + +- **`jest.setup.ts`** — global hooks and expected-error suppression; read before changing console behavior. + +## References + +- `.cursor/rules/testing.mdc` diff --git a/skills/typescript/SKILL.md b/skills/typescript/SKILL.md deleted file mode 100644 index ddacabce..00000000 --- a/skills/typescript/SKILL.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: typescript -description: Use for TypeScript source layout, Rollup build, and dist outputs in contentstack-typescript. ---- - -# TypeScript & layout – contentstack-typescript - -## When to use - -- Editing `src/` modules or `config/tsconfig.decl-esm.json` -- Debugging ESM vs CJS consumption issues - -## Instructions - -### Structure - -- Application source under **`src/`**; Rollup config at repo root (`rollup` `-c`). -- Types and JS emit land under **`dist/modern/`**—do not hand-edit generated files. - -### Node version - -- **`engines.node`** requires **≥ 18**—use the same for local development to avoid subtle test or tooling differences. diff --git a/src/common/types.ts b/src/common/types.ts index d6eefd07..b4dfea59 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -21,25 +21,25 @@ export type queryParams = { /** * Interface for creating Contentstack plugins - * + * * @example * ```typescript * import { ContentstackPlugin } from '@contentstack/delivery-sdk'; - * + * * class MyPlugin implements ContentstackPlugin { * onRequest(config: any): any { * // Modify request configuration * console.log('Processing request:', config.url); * return { ...config, headers: { ...config.headers, 'X-Custom-Header': 'value' } }; * } - * + * * onResponse(request: any, response: any, data: any): any { * // Process response data * console.log('Processing response:', response.status); * return { ...response, data: { ...data, processed: true } }; * } * } - * + * * const stack = contentstack.stack({ * apiKey: 'your-api-key', * deliveryToken: 'your-delivery-token', @@ -344,8 +344,6 @@ export interface FindResponse { assets?: T[]; global_fields?: T[]; count?: number; - taxonomies?: T[]; - terms?: T[]; } export interface LivePreviewQuery { @@ -369,31 +367,3 @@ export type LivePreview = { management_token?: string; preview_token?: string; }; - -export interface BaseTaxonomy { - uid: string; - name: string; - description?: string; - terms_count?: number; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string; - type: string; - ACL: ACL; - publish_details?: PublishDetails; -} - -export interface BaseTerm { - taxonomy_uid: string; - uid: string; - name: string; - created_by: string; - created_at: string; - updated_by: string; - updated_at: string; - children_count?: number; - depth?: number; - ACL: ACL; - publish_details?: PublishDetails; -} \ No newline at end of file diff --git a/src/common/utils.ts b/src/common/utils.ts index ca86d9b9..24d4d0f4 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -37,24 +37,3 @@ export function encodeQueryParams(params: params): params { return encodedParams; } - -/** - * Builds request headers for entry variant requests. - * @param variants - Comma-separated variant UID(s) - * @param branch - Optional branch name to scope the variant request - */ -export function buildVariantRequestHeaders( - variants: string, - branch?: string -): Record | undefined { - const headers: Record = {}; - - if (variants) { - headers['x-cs-variant-uid'] = variants; - } - if (branch) { - headers.branch = branch; - } - - return Object.keys(headers).length > 0 ? headers : undefined; -} diff --git a/src/entries/entries.ts b/src/entries/entries.ts index 09f8c568..7eda6068 100644 --- a/src/entries/entries.ts +++ b/src/entries/entries.ts @@ -2,7 +2,7 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { Query } from '../query'; import { BaseQuery } from '../query'; import { FindResponse } from '../common/types'; -import { buildVariantRequestHeaders, encodeQueryParams } from '../common/utils'; +import { encodeQueryParams } from '../common/utils'; import { ErrorMessages } from '../common/error-messages'; export class Entries extends BaseQuery { @@ -14,7 +14,6 @@ export class Entries extends BaseQuery { this._contentTypeUid = contentTypeUid; this._urlPath = `/content_types/${this._contentTypeUid}/entries`; this._variants = ''; - this._variantsBranch = ''; } /** @@ -253,36 +252,28 @@ export class Entries extends BaseQuery { * const query = stack.contentType("contentTypeUid").entry().query(); */ query(queryObj?: { [key: string]: any }) { - if (queryObj) { - return new Query(this._client, this._parameters, this._queryParams, this._variants, this._contentTypeUid, this._variantsBranch, queryObj); - } + if (queryObj) return new Query(this._client, this._parameters, this._queryParams, this._variants, this._contentTypeUid, queryObj); - return new Query(this._client, this._parameters, this._queryParams, this._variants, this._contentTypeUid, this._variantsBranch); + return new Query(this._client, this._parameters, this._queryParams, this._variants, this._contentTypeUid); } /** * @method variants * @memberof Entries - * @description Stores the variant UID(s) and optional branch name, which are sent as the `x-cs-variant-uid` and `branch` headers on the request when find() is called. - * @param {string | string[]} variants - Variant UID or UIDs - * @param {string} [branchName] - Optional branch name sent as the `branch` header + * @description The variant header will be added to axios client * @returns {Entries} * @example * import contentstack from '@contentstack/delivery-sdk' * * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); * const result = await stack.contentType('abc').entry().variants('xyz').find(); - * const resultWithBranch = await stack.contentType('abc').entry().variants('xyz', 'branch_name').find(); */ - variants(variants: string | string[], branchName?: string): Entries { + variants(variants: string | string[]): Entries { if (Array.isArray(variants) && variants.length > 0) { this._variants = variants.join(','); } else if (typeof variants == 'string' && variants.length > 0) { this._variants = variants; } - if (typeof branchName === 'string' && branchName.length > 0) { - this._variantsBranch = branchName; - } return this; } @@ -329,11 +320,10 @@ export class Entries extends BaseQuery { contentTypeUid: this._contentTypeUid }; - const variantHeaders = buildVariantRequestHeaders(this._variants, this._variantsBranch); - if (variantHeaders) { + if (this._variants) { getRequestOptions.headers = { ...getRequestOptions.headers, - ...variantHeaders + 'x-cs-variant-uid': this._variants }; } const response = await getData(this._client, this._urlPath, getRequestOptions); diff --git a/src/entries/entry.ts b/src/entries/entry.ts index 01a43b27..a7b6e5cd 100644 --- a/src/entries/entry.ts +++ b/src/entries/entry.ts @@ -1,6 +1,5 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { ErrorMessages } from '../common/error-messages'; -import { buildVariantRequestHeaders } from '../common/utils'; interface EntryResponse { entry: T; @@ -11,7 +10,6 @@ export class Entry { private _entryUid: string; private _urlPath: string; protected _variants: string; - protected _variantsBranch: string; _queryParams: { [key: string]: string | number | string[] } = {}; constructor(client: AxiosInstance, contentTypeUid: string, entryUid: string) { this._client = client; @@ -19,7 +17,6 @@ export class Entry { this._entryUid = entryUid; this._urlPath = `/content_types/${this._contentTypeUid}/entries/${this._entryUid}`; this._variants = ''; - this._variantsBranch = ''; } /** @@ -42,26 +39,20 @@ export class Entry { /** * @method variants * @memberof Entry - * @description Stores the variant UID(s) and optional branch name, which are sent as the `x-cs-variant-uid` and `branch` headers on the request when fetch() is called. - * @param {string | string[]} variants - Variant UID or UIDs - * @param {string} [branchName] - Optional branch name sent as the `branch` header + * @description The variant header will be added to axios client * @returns {Entry} * @example * import contentstack from '@contentstack/delivery-sdk' * * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); * const result = await stack.contentType('abc').entry('entry_uid').variants('xyz').fetch(); - * const resultWithBranch = await stack.contentType('abc').entry('entry_uid').variants('xyz', 'branch_name').fetch(); */ - variants(variants: string | string[], branchName?: string): this { + variants(variants: string | string[]): this { if (Array.isArray(variants) && variants.length > 0) { this._variants = variants.join(','); } else if (typeof variants == 'string' && variants.length > 0) { this._variants = variants; } - if (typeof branchName === 'string' && branchName.length > 0) { - this._variantsBranch = branchName; - } return this; } @@ -198,11 +189,10 @@ export class Entry { contentTypeUid: this._contentTypeUid, entryUid: this._entryUid }; - const variantHeaders = buildVariantRequestHeaders(this._variants, this._variantsBranch); - if (variantHeaders) { + if (this._variants) { getRequestOptions.headers = { ...getRequestOptions.headers, - ...variantHeaders + 'x-cs-variant-uid': this._variants }; } diff --git a/src/index.ts b/src/index.ts index fe851ca4..3891e499 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,6 @@ export type { ImageTransform } from './assets'; export type { AssetQuery } from './query'; export type { TaxonomyQuery } from './query'; export type { ContentTypeQuery } from './query'; -export type { Taxonomy } from './taxonomy'; export { ErrorMessages, ErrorCode } from './common/error-messages'; export default contentstack; diff --git a/src/query/base-query.ts b/src/query/base-query.ts index 05418b36..0d76cc0e 100644 --- a/src/query/base-query.ts +++ b/src/query/base-query.ts @@ -1,7 +1,7 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { Pagination } from '../common/pagination'; import { FindResponse, params } from '../common/types'; -import { buildVariantRequestHeaders, encodeQueryParams } from '../common/utils'; +import { encodeQueryParams } from '../common/utils'; import type { Query } from './query'; export class BaseQuery extends Pagination { @@ -10,7 +10,6 @@ export class BaseQuery extends Pagination { protected _client!: AxiosInstance; protected _urlPath!: string; protected _variants!: string; - protected _variantsBranch!: string; /** * Helper method to cast this instance to Query type @@ -232,11 +231,10 @@ export class BaseQuery extends Pagination { contentTypeUid: this.extractContentTypeUidFromUrl() }; - const variantHeaders = buildVariantRequestHeaders(this._variants, this._variantsBranch); - if (variantHeaders) { + if (this._variants) { getRequestOptions.headers = { ...getRequestOptions.headers, - ...variantHeaders + 'x-cs-variant-uid': this._variants }; } const response = await getData(this._client, this._urlPath, getRequestOptions); diff --git a/src/query/contenttype-query.ts b/src/query/contenttype-query.ts index 0612c8f9..e2959ba8 100644 --- a/src/query/contenttype-query.ts +++ b/src/query/contenttype-query.ts @@ -1,5 +1,5 @@ -import { BaseQuery } from './base-query'; import { AxiosInstance } from '@contentstack/core'; +import { BaseQuery } from './base-query'; export class ContentTypeQuery extends BaseQuery { constructor(client: AxiosInstance) { diff --git a/src/query/query.ts b/src/query/query.ts index edec8fd7..fbfc92e6 100644 --- a/src/query/query.ts +++ b/src/query/query.ts @@ -1,38 +1,13 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { BaseQuery } from './base-query'; import { BaseQueryParameters, QueryOperation, QueryOperator, TaxonomyQueryOperation, params, queryParams, FindResponse } from '../common/types'; -import { buildVariantRequestHeaders, encodeQueryParams } from '../common/utils'; +import { encodeQueryParams } from '../common/utils'; import { ErrorMessages } from '../common/error-messages'; export class Query extends BaseQuery { private _contentTypeUid?: string; - constructor( - client: AxiosInstance, - params: params, - queryParams: queryParams, - variants?: string, - uid?: string, - queryObj?: { [key: string]: any } - ); - constructor( - client: AxiosInstance, - params: params, - queryParams: queryParams, - variants?: string, - uid?: string, - variantsBranch?: string, - queryObj?: { [key: string]: any } - ); - constructor( - client: AxiosInstance, - params: params, - queryParams: queryParams, - variants?: string, - uid?: string, - variantsBranchOrQueryObj?: string | { [key: string]: any }, - queryObj?: { [key: string]: any } - ) { + constructor(client: AxiosInstance, params: params, queryParams: queryParams, variants?: string, uid?: string, queryObj?: { [key: string]: any }) { super(); this._client = client; this._contentTypeUid = uid; @@ -41,23 +16,11 @@ export class Query extends BaseQuery { this._queryParams = queryParams || {}; this._variants = variants || ''; - let variantsBranch = ''; - let resolvedQueryObj: { [key: string]: any } | undefined; - - if (typeof variantsBranchOrQueryObj === 'string') { - variantsBranch = variantsBranchOrQueryObj; - resolvedQueryObj = queryObj; - } else if (variantsBranchOrQueryObj) { - resolvedQueryObj = variantsBranchOrQueryObj; - } - - this._variantsBranch = variantsBranch; - if (!uid) { this._urlPath = `/assets`; } - if (resolvedQueryObj) { - this._parameters = { ...this._parameters, ...resolvedQueryObj }; + if (queryObj) { + this._parameters = { ...this._parameters, ...queryObj }; } } // Validate if input is alphanumeric @@ -688,11 +651,10 @@ export class Query extends BaseQuery { contentTypeUid: this._contentTypeUid }; - const variantHeaders = buildVariantRequestHeaders(this._variants, this._variantsBranch); - if (variantHeaders) { + if (this._variants) { getRequestOptions.headers = { ...getRequestOptions.headers, - ...variantHeaders + 'x-cs-variant-uid': this._variants }; } const response = await getData(this._client, this._urlPath, getRequestOptions); diff --git a/src/query/taxonomy-query.ts b/src/query/taxonomy-query.ts index 7b4c345d..dfa2ca9e 100644 --- a/src/query/taxonomy-query.ts +++ b/src/query/taxonomy-query.ts @@ -1,31 +1,10 @@ import { Query } from "./query"; -import { AxiosInstance, getData } from "@contentstack/core"; -import { FindResponse } from "../common/types"; +import { AxiosInstance } from "@contentstack/core"; export class TaxonomyQuery extends Query { - constructor(client: AxiosInstance) { - super(client, {}, {}); // will need make changes to Query class so that CT uid is not mandatory - this._client = client; - this._urlPath = `/taxonomies/entries`; - } - /** - * @method find - * @memberof TaxonomyQuery - * @description Fetches a list of all published taxonomies available in the stack. - * @returns {Promise>} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const taxonomyQuery = stack.taxonomy(); - * const result = await taxonomyQuery.find(); - */ - override async find(): Promise> { - this._urlPath = "/taxonomies"; - const response = await getData(this._client, this._urlPath, { - params: this._queryParams, - }); - - return response as FindResponse; - } + constructor(client: AxiosInstance) { + super(client, {}, {}); // will need make changes to Query class so that CT uid is not mandatory + this._client = client; + this._urlPath = `/taxonomies/entries`; + } }; diff --git a/src/query/term-query.ts b/src/query/term-query.ts deleted file mode 100644 index 8c8076f6..00000000 --- a/src/query/term-query.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { AxiosInstance, getData } from '@contentstack/core'; -import { FindResponse } from '../common/types'; - -/** - * @class TermQuery - * @description Represents a query for fetching multiple published terms from a taxonomy. Requires taxonomy_publish feature flag to be enabled. - */ -export class TermQuery { - private _taxonomyUid: string; - private _client: AxiosInstance; - private _urlPath: string; - _queryParams: { [key: string]: string | number } = {}; - - /** - * @constructor - * @param {AxiosInstance} client - The HTTP client instance - * @param {string} taxonomyUid - The taxonomy UID - */ - constructor(client: AxiosInstance, taxonomyUid: string) { - this._client = client; - this._taxonomyUid = taxonomyUid; - this._urlPath = `/taxonomies/${this._taxonomyUid}/terms`; - } - - /** - * @method depth - * @memberof TermQuery - * @description Limits how many levels of the term tree are resolved. - * @param {number} depth - The depth limit - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().depth(2).find(); - */ - depth(depth: number): TermQuery { - this._queryParams.depth = depth; - - return this; - } - - /** - * @method skip - * @memberof TermQuery - * @description Skips the specified number of terms (pagination). - * @param {number} skip - The number of terms to skip - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().skip(10).find(); - */ - skip(skip: number): TermQuery { - this._queryParams.skip = skip; - - return this; - } - - /** - * @method limit - * @memberof TermQuery - * @description Limits the number of terms returned (pagination). - * @param {number} limit - The maximum number of terms to return - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().limit(10).find(); - */ - limit(limit: number): TermQuery { - this._queryParams.limit = limit; - - return this; - } - - /** - * @method includeCount - * @memberof TermQuery - * @description Includes a count field in the response. - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().includeCount().find(); - */ - includeCount(): TermQuery { - this._queryParams.include_count = 'true'; - - return this; - } - - /** - * @method includeFallback - * @memberof TermQuery - * @description Falls back through the branch locale hierarchy when a term is not published in the requested locale. - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().includeFallback().find(); - */ - includeFallback(): TermQuery { - this._queryParams.include_fallback = 'true'; - - return this; - } - - /** - * @method includeBranch - * @memberof TermQuery - * @description Adds a _branch field to the response objects. - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().includeBranch().find(); - */ - includeBranch(): TermQuery { - this._queryParams.include_branch = 'true'; - - return this; - } - - /** - * @method param - * @memberof TermQuery - * @description Adds a single query parameter to the request. - * @param {string} key - The parameter key - * @param {string | number} value - The parameter value - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().param('key', 'value').find(); - */ - param(key: string, value: string | number): TermQuery { - this._queryParams[key] = value; - - return this; - } - - /** - * @method addParams - * @memberof TermQuery - * @description Adds multiple query parameters to the request. - * @param {object} paramObj - The parameters to add - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().addParams({ key: 'value' }).find(); - */ - addParams(paramObj: { [key: string]: string | number }): TermQuery { - this._queryParams = { ...this._queryParams, ...paramObj }; - - return this; - } - - /** - * @method locale - * @memberof TermQuery - * @description Retrieves terms published in the specified locale. - * @param {string} locale - The locale code (e.g. 'hi-in', 'en-us') - * @returns {TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().locale('hi-in').find(); - */ - locale(locale: string): TermQuery { - this._queryParams.locale = locale; - return this; - } - - /** - * @method find - * @memberof TermQuery - * @description Fetches a list of all published terms within a specific taxonomy. - * @returns {Promise>} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term().find(); - */ - async find(): Promise> { - const response = await getData(this._client, this._urlPath, { params: this._queryParams }); - return response as FindResponse; - } -} diff --git a/src/stack/stack.ts b/src/stack/stack.ts index 6413ec90..1b3c867a 100644 --- a/src/stack/stack.ts +++ b/src/stack/stack.ts @@ -8,7 +8,6 @@ import { synchronization } from '../sync'; import { TaxonomyQuery } from '../query'; import { GlobalFieldQuery } from '../query'; import { GlobalField } from '../global-field'; -import { Taxonomy } from '../taxonomy'; export class Stack { readonly config: StackConfig; @@ -79,11 +78,7 @@ export class Stack { * const taxonomy = stack.taxonomy() // For taxonomy query object */ - taxonomy(): TaxonomyQuery; - taxonomy(uid: string): Taxonomy; - taxonomy(uid?: string): Taxonomy | TaxonomyQuery { - if (uid) return new Taxonomy(this._client, uid); - + taxonomy(): TaxonomyQuery { return new TaxonomyQuery(this._client); } diff --git a/src/taxonomy/index.ts b/src/taxonomy/index.ts deleted file mode 100644 index c84db4b7..00000000 --- a/src/taxonomy/index.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { AxiosInstance, getData } from '@contentstack/core'; -import { TermQuery } from '../query/term-query'; -import { Term } from './term'; - -/** - * @class Taxonomy - * @description Represents a published taxonomy with methods to fetch taxonomy data and manage terms. Requires taxonomy_publish feature flag to be enabled. - */ -export class Taxonomy { - private _client: AxiosInstance; - private _taxonomyUid: string; - private _urlPath: string; - - _queryParams: { [key: string]: string | number } = {}; - - /** - * @constructor - * @param {AxiosInstance} client - The HTTP client instance - * @param {string} taxonomyUid - The taxonomy UID - */ - constructor(client: AxiosInstance, taxonomyUid: string) { - this._client = client; - this._taxonomyUid = taxonomyUid; - this._urlPath = `/taxonomies/${this._taxonomyUid}`; - } - - /** - * @method term - * @memberof Taxonomy - * @description Gets a specific term or creates a term query - * @param {string} [uid] - Optional term UID. If provided, returns a Term instance. If not provided, returns a TermQuery instance. - * @returns {Term | TermQuery} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * // Get a specific term - * const term = stack.taxonomy('taxonomy_uid').term('term_uid'); - * // Get all terms - * const termQuery = stack.taxonomy('taxonomy_uid').term(); - */ - term(uid: string): Term; - term(): TermQuery; - term(uid?: string): Term | TermQuery { - if (uid) return new Term(this._client, this._taxonomyUid, uid); - - return new TermQuery(this._client, this._taxonomyUid); - } - - /** - * @method includeFallback - * @memberof Taxonomy - * @description Falls back through the branch locale hierarchy when the taxonomy is not published in the requested locale. - * @returns {Taxonomy} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').includeFallback().fetch(); - */ - includeFallback(): Taxonomy { - this._queryParams.include_fallback = 'true'; - - return this; - } - - /** - * @method includeBranch - * @memberof Taxonomy - * @description Adds a _branch field to the response object. - * @returns {Taxonomy} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').includeBranch().fetch(); - */ - includeBranch(): Taxonomy { - this._queryParams.include_branch = 'true'; - - return this; - } - - /** - * @method param - * @memberof Taxonomy - * @description Adds a single query parameter to the request. - * @param {string} key - The parameter key - * @param {string | number} value - The parameter value - * @returns {Taxonomy} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').param('key', 'value').fetch(); - */ - param(key: string, value: string | number): Taxonomy { - this._queryParams[key] = value; - - return this; - } - - /** - * @method addParams - * @memberof Taxonomy - * @description Adds multiple query parameters to the request. - * @param {object} paramObj - The parameters to add - * @returns {Taxonomy} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').addParams({ key: 'value' }).fetch(); - */ - addParams(paramObj: { [key: string]: string | number }): Taxonomy { - this._queryParams = { ...this._queryParams, ...paramObj }; - - return this; - } - - /** - * @method fetch - * @memberof Taxonomy - * @description Fetches the taxonomy data by UID. Use param() or addParams() to pass locale or other query parameters. - * @returns {Promise} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').fetch(); - * const localized = await stack.taxonomy('taxonomy_uid').param('locale', 'hi-in').fetch(); - */ - async fetch(): Promise { - const response = await getData(this._client, this._urlPath, { params: this._queryParams }); - - if (response.taxonomy) return response.taxonomy as T; - - return response; - } -} diff --git a/src/taxonomy/term.ts b/src/taxonomy/term.ts deleted file mode 100644 index 690845e6..00000000 --- a/src/taxonomy/term.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { AxiosInstance, getData } from "@contentstack/core"; - -/** - * @class Term - * @description Represents a published taxonomy term with methods to fetch term data, locales, ancestors, and descendants. Requires taxonomy_publish feature flag to be enabled. - */ -export class Term { - protected _client: AxiosInstance; - private _taxonomyUid: string; - private _termUid: string; - private _urlPath: string; - - _queryParams: { [key: string]: string | number } = {}; - - /** - * @constructor - * @param {AxiosInstance} client - The HTTP client instance - * @param {string} taxonomyUid - The taxonomy UID - * @param {string} termUid - The term UID - */ - constructor(client: AxiosInstance, taxonomyUid: string, termUid: string) { - this._client = client; - this._taxonomyUid = taxonomyUid; - this._termUid = termUid; - this._urlPath = `/taxonomies/${this._taxonomyUid}/terms/${this._termUid}`; - } - - /** - * @method depth - * @memberof Term - * @description Limits how many levels of ancestors/descendants are resolved. Applies to the ancestors() and descendants() endpoints. - * @param {number} depth - The depth limit - * @returns {Term} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').depth(2).descendants(); - */ - depth(depth: number): Term { - this._queryParams.depth = depth; - - return this; - } - - /** - * @method includeFallback - * @memberof Term - * @description Falls back through the branch locale hierarchy when the term is not published in the requested locale. - * @returns {Term} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').includeFallback().fetch(); - */ - includeFallback(): Term { - this._queryParams.include_fallback = 'true'; - - return this; - } - - /** - * @method includeBranch - * @memberof Term - * @description Adds a _branch field to the response object. - * @returns {Term} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').includeBranch().fetch(); - */ - includeBranch(): Term { - this._queryParams.include_branch = 'true'; - - return this; - } - - /** - * @method param - * @memberof Term - * @description Adds a single query parameter to the request. - * @param {string} key - The parameter key - * @param {string | number} value - The parameter value - * @returns {Term} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').param('key', 'value').fetch(); - */ - param(key: string, value: string | number): Term { - this._queryParams[key] = value; - - return this; - } - - /** - * @method addParams - * @memberof Term - * @description Adds multiple query parameters to the request. - * @param {object} paramObj - The parameters to add - * @returns {Term} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').addParams({ key: 'value' }).fetch(); - */ - addParams(paramObj: { [key: string]: string | number }): Term { - this._queryParams = { ...this._queryParams, ...paramObj }; - - return this; - } - - /** - * @method locales - * @memberof Term - * @description Fetches all published, localized versions of a single term. - * @returns {Promise} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').locales(); - */ - async locales(): Promise { - const response = await getData(this._client, `${this._urlPath}/locales`, { params: this._queryParams }); - if (response.locales) return response.locales as T; - return response; - } - - /** - * @method ancestors - * @memberof Term - * @description Fetches all ancestors of a single published term, up to the root. - * @returns {Promise} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').ancestors(); - */ - async ancestors(): Promise { - const response = await getData(this._client, `${this._urlPath}/ancestors`, { params: this._queryParams }); - if (response.ancestors) return response.ancestors as T; - return response; - } - - /** - * @method descendants - * @memberof Term - * @description Fetches all descendants of a single published term. - * @returns {Promise} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').descendants(); - */ - async descendants(): Promise { - const response = await getData(this._client, `${this._urlPath}/descendants`, { params: this._queryParams }); - if (response.descendants) return response.descendants as T; - return response; - } - - /** - * @method fetch - * @memberof Term - * @description Fetches a single published term. Use param() or addParams() to pass locale or other query parameters. - * @returns {Promise} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').fetch(); - * const localized = await stack.taxonomy('taxonomy_uid').term('term_uid').param('locale', 'hi-in').fetch(); - */ - async fetch(): Promise { - const response = await getData(this._client, this._urlPath, { params: this._queryParams }); - if (response.term) return response.term as T; - return response; - } -} diff --git a/test/api/asset-management.spec.ts b/test/api/asset-management.spec.ts index 9bba7ee8..82bd8182 100644 --- a/test/api/asset-management.spec.ts +++ b/test/api/asset-management.spec.ts @@ -337,9 +337,7 @@ describe('Asset Management Tests', () => { console.log('Non-existent asset properly rejected:', (error as Error).message); // Should handle gracefully } - // Non-prod regions can be slow to resolve a bogus asset UID; allow 60s so this - // error-path test rejects/resolves within timeout instead of flaking. - }, 60000); + }); it('should handle empty asset queries', async () => { const result = await stack diff --git a/test/api/asset-query.spec.ts b/test/api/asset-query.spec.ts index 10f957dc..3fcbe8b3 100644 --- a/test/api/asset-query.spec.ts +++ b/test/api/asset-query.spec.ts @@ -23,9 +23,7 @@ describe("AssetQuery API tests", () => { it("should check for include dimensions", async () => { const result = await makeAssetQuery().includeDimension().find(); if (result.assets) { - // dimension is only present on image assets; the first asset may be a video/pdf/etc. - const imageAsset = result.assets.find((a: any) => String(a.content_type).startsWith("image/")) || result.assets[0]; - expect(imageAsset.dimension).toBeDefined(); + expect(result.assets[0].dimension).toBeDefined(); expect(result.assets[0]._version).toBeDefined(); expect(result.assets[0].uid).toBeDefined(); expect(result.assets[0].content_type).toBeDefined(); diff --git a/test/api/deep-references.spec.ts b/test/api/deep-references.spec.ts index 27deff01..f9756438 100644 --- a/test/api/deep-references.spec.ts +++ b/test/api/deep-references.spec.ts @@ -282,9 +282,9 @@ describe('Deep Reference Chains Tests', () => { .contentType(COMPLEX_CT) .entry(COMPLEX_ENTRY_UID!) .includeReference([ - 'single_ref', - 'multi_ref', - 'self_ref' + 'related_content', + 'authors', + 'page_footer' ]) .fetch(); @@ -319,14 +319,14 @@ describe('Deep Reference Chains Tests', () => { }; // Analyze root level reference fields - if (result.single_ref) { - analyzeReferenceTypes(result.single_ref); + if (result.related_content) { + analyzeReferenceTypes(result.related_content); } - if (result.multi_ref) { - analyzeReferenceTypes(result.multi_ref); + if (result.authors) { + analyzeReferenceTypes(result.authors); } - if (result.self_ref) { - analyzeReferenceTypes(result.self_ref); + if (result.page_footer) { + analyzeReferenceTypes(result.page_footer); } console.log('Reference type distribution:', referenceTypes); diff --git a/test/api/entries.spec.ts b/test/api/entries.spec.ts index 3c1677e9..9d8c799b 100644 --- a/test/api/entries.spec.ts +++ b/test/api/entries.spec.ts @@ -18,13 +18,6 @@ const stack = stackInstance(); const BLOG_POST_CT = process.env.MEDIUM_CONTENT_TYPE_UID || 'article'; const SOURCE_CT = process.env.COMPLEX_CONTENT_TYPE_UID || 'cybersecurity'; -// Taxonomy test data - uses real taxonomy terms from the test stack -// USA taxonomy: california > san_diago, san_jose -// India taxonomy: maharashtra > mumbai, pune -const TAX_FIELD = 'taxonomies.usa'; -const TAX_TERM = process.env.TAX_USA_STATE || 'california'; -const TAX_CHILD_TERM = 'san_diago'; - describe("Entries API test cases", () => { it("should check for entries is defined", async () => { const result = await makeEntries(BLOG_POST_CT).find(); @@ -120,55 +113,60 @@ describe("Entries API test cases", () => { }); it("CT Taxonomies Query: Get Entries With One Term", async () => { - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EQUALS, TAX_TERM); + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EQUALS, "term_one"); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Any Term ($in)", async () => { - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.INCLUDES, [TAX_TERM, TAX_CHILD_TERM]); + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.INCLUDES, ["term_one","term_two",]); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Any Term ($or)", async () => { - let Query1 = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EQUALS, TAX_TERM); - let Query2 = makeEntries(SOURCE_CT).query().where("taxonomies.india", QueryOperation.EQUALS, process.env.TAX_INDIA_STATE || "maharashtra"); + let Query1 = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EQUALS, "term_one"); + let Query2 = makeEntries(SOURCE_CT).query().where("taxonomies.two", QueryOperation.EQUALS, "term_two"); let Query = makeEntries(SOURCE_CT).query().queryOperator(QueryOperator.OR, Query1, Query2); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With All Terms ($and)", async () => { - let Query1 = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EQUALS, TAX_TERM); - let Query2 = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EXISTS, true); + let Query1 = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EQUALS, "term_one"); + let Query2 = makeEntries(SOURCE_CT).query().where("taxonomies.two", QueryOperation.EQUALS, "term_two"); let Query = makeEntries(SOURCE_CT).query().queryOperator(QueryOperator.AND, Query1, Query2); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Any Taxonomy Terms ($exists)", async () => { - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EXISTS, true); + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EXISTS, true); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Taxonomy Terms and Also Matching Its Children Term ($eq_below, level)", async () => { - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.EQ_BELOW, TAX_TERM, { levels: 1 }); + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.EQ_BELOW, "term_one", { levels: 1, + }); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Taxonomy Terms Children's and Excluding the term itself ($below, level)", async () => { - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.BELOW, TAX_TERM, { levels: 1 }); + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.BELOW, "term_one", { levels: 1 }); const data = await Query.find(); + // May return 0 entries if no entries are tagged with children of term_one if (data.entries) { expect(data.entries.length).toBeGreaterThanOrEqual(0); + if (data.entries.length === 0) { + console.log('⚠️ No entries found with taxonomy children of term_one - test data dependent'); + } } }); it("CT Taxonomies Query: Get Entries With Taxonomy Terms and Also Matching Its Parent Term ($eq_above, level)", async () => { - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.EQ_ABOVE, TAX_CHILD_TERM, { levels: 1 }); + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.EQ_ABOVE, "term_one", { levels: 1 }); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); @@ -188,10 +186,20 @@ describe("Entries API test cases", () => { it("CT Taxonomies Query: Get Entries With Taxonomy Terms Parent and Excluding the term itself ($above, level)", async () => { // ABOVE operation finds entries tagged with PARENT terms of the given term - // Using san_diago (child of california) to find its parent - let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.ABOVE, TAX_CHILD_TERM, { levels: 1 }); - const data = await Query.find(); - if (data.entries) expect(data.entries.length).toBeGreaterThanOrEqual(0); + // Requires a child term (e.g., term_one_child) to find its parents + try { + let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.ABOVE, "term_one_child", { levels: 1 }); + const data = await Query.find(); + if (data.entries) expect(data.entries.length).toBeGreaterThanOrEqual(0); + } catch (error: any) { + // Handle gracefully if term_one_child doesn't exist or API doesn't support ABOVE + if (error.status === 400 || error.status === 422 || error.status === 141) { + console.log(`⚠️ TaxonomyQueryOperation.ABOVE returned ${error.status} - term_one_child may not exist or ABOVE not supported`); + expect([400, 422, 141]).toContain(error.status); + } else { + throw error; + } + } }); }); function makeEntries(contentTypeUid = ""): Entries { diff --git a/test/api/entry-variants-branch.spec.ts b/test/api/entry-variants-branch.spec.ts deleted file mode 100644 index 062811f9..00000000 --- a/test/api/entry-variants-branch.spec.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { describe, it, expect } from '@jest/globals'; -import * as contentstack from '../../src/stack'; -import { stackInstance } from '../utils/stack-instance'; -import { TEntry } from './types'; - -const stack = stackInstance(); - -const contentTypeUid = process.env.COMPLEX_CONTENT_TYPE_UID || 'cybersecurity'; -const entryUid = process.env.COMPLEX_ENTRY_UID || ''; -const variantUid = process.env.VARIANT_UID || ''; -const branchUid = process.env.BRANCH_UID || 'main'; - -const hasEntryUid = !!entryUid; -const hasVariantUid = !!variantUid; - -const skipIfNoEntry = !hasEntryUid ? describe.skip : describe; -const skipIfNoVariant = !hasVariantUid ? describe.skip : describe; -const skipIfNoVariantOrEntry = !hasEntryUid || !hasVariantUid ? describe.skip : describe; - -describe('Entry Variants with Branch API Tests', () => { - skipIfNoVariantOrEntry('Single entry fetch with variant and branch', () => { - it('should fetch entry with single variant UID and branch', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid, branchUid) - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - - it('should fetch entry with multiple variant UIDs and branch', async () => { - const variantUids = [variantUid, 'variant_2', 'variant_3']; - - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUids, branchUid) - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - - it('should fetch entry with variant only when branch is omitted (backward compatible)', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid) - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - - it('should fetch entry with variant, branch, and includeBranch metadata', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid, branchUid) - .includeBranch() - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - if (result._branch) { - expect(typeof result._branch).toBe('string'); - } - }); - - it('should fetch entry with variant, branch, and includeMetadata', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid, branchUid) - .includeMetadata() - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - }); - - skipIfNoVariant('Entries find with variant and branch', () => { - it('should find entries with single variant UID and branch', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry() - .variants(variantUid, branchUid) - .limit(5) - .find(); - - expect(result).toBeDefined(); - expect(result.entries).toBeDefined(); - expect(Array.isArray(result.entries)).toBe(true); - - if (result.entries!.length > 0) { - expect(result.entries![0].uid).toBeDefined(); - } - }); - - it('should find entries with multiple variant UIDs and branch', async () => { - const variantUids = [variantUid, 'variant_2']; - - const result = await stack - .contentType(contentTypeUid) - .entry() - .variants(variantUids, branchUid) - .limit(5) - .find(); - - expect(result).toBeDefined(); - expect(result.entries).toBeDefined(); - expect(Array.isArray(result.entries)).toBe(true); - }); - - it('should find entries with variant only when branch is omitted (backward compatible)', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry() - .variants(variantUid) - .limit(5) - .find(); - - expect(result).toBeDefined(); - expect(result.entries).toBeDefined(); - }); - - it('should find entries with variant, branch, and includeCount', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry() - .variants(variantUid, branchUid) - .includeCount() - .limit(5) - .find(); - - expect(result).toBeDefined(); - expect(result.entries).toBeDefined(); - expect(typeof result.count).toBe('number'); - }); - }); - - skipIfNoVariantOrEntry('Query chain with variant and branch', () => { - it('should query entries with variant and branch via query()', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry() - .variants(variantUid, branchUid) - .query() - .equalTo('uid', entryUid) - .find(); - - expect(result).toBeDefined(); - expect(result.entries).toBeDefined(); - - if (result.entries && result.entries.length > 0) { - result.entries.forEach((entry) => { - expect(entry.uid).toBe(entryUid); - }); - } - }); - - it('should query entries with variant, branch, and pagination', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry() - .variants(variantUid, branchUid) - .limit(3) - .skip(0) - .find(); - - expect(result).toBeDefined(); - expect(result.entries).toBeDefined(); - expect(result.entries!.length).toBeLessThanOrEqual(3); - }); - }); - - skipIfNoEntry('Branch optional behavior', () => { - it('should fetch entry without variant or branch headers when neither is set', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - }); - - skipIfNoVariantOrEntry('Stack-level branch vs variants-level branch', () => { - it('should fetch entry when stack has default branch and variants() also passes branch', async () => { - const stackWithBranch = contentstack.stack({ - host: process.env.HOST || '', - apiKey: process.env.API_KEY || '', - deliveryToken: process.env.DELIVERY_TOKEN || '', - environment: process.env.ENVIRONMENT || '', - branch: branchUid, - live_preview: { - enable: false, - preview_token: process.env.PREVIEW_TOKEN || '', - host: process.env.LIVE_PREVIEW_HOST || '', - }, - }); - - expect(stackWithBranch.config.branch).toBe(branchUid); - - const result = await stackWithBranch - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid, branchUid) - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - - it('should fetch entry with variant+branch on stack without stack-level branch config', async () => { - const result = await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid, branchUid) - .fetch(); - - expect(result).toBeDefined(); - expect(result.uid).toBe(entryUid); - }); - }); - - skipIfNoVariantOrEntry('Error handling', () => { - it('should handle invalid variant UID with branch gracefully', async () => { - try { - await stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants('invalid_variant_uid', branchUid) - .fetch(); - } catch (error) { - expect(error).toBeDefined(); - } - }); - - it('should return consistent results for repeated variant+branch requests', async () => { - const fetchEntry = () => - stack - .contentType(contentTypeUid) - .entry(entryUid) - .variants(variantUid, branchUid) - .fetch(); - - const [result1, result2] = await Promise.all([fetchEntry(), fetchEntry()]); - - expect(result1.uid).toBe(entryUid); - expect(result2.uid).toBe(entryUid); - expect(result1.uid).toBe(result2.uid); - }); - }); -}); diff --git a/test/api/query-operators-comprehensive.spec.ts b/test/api/query-operators-comprehensive.spec.ts index 41ada493..02df383a 100644 --- a/test/api/query-operators-comprehensive.spec.ts +++ b/test/api/query-operators-comprehensive.spec.ts @@ -438,20 +438,20 @@ describe('Query Operators - Comprehensive Coverage', () => { .contentType(COMPLEX_CT) .entry() .query() - .referenceIn('single_ref', authorQuery) + .referenceIn('authors', authorQuery) .find(); expect(result).toBeDefined(); - + if (result.entries && result.entries?.length > 0) { - console.log(`Found ${result.entries?.length} entries with referenceIn single_ref`); - - // Verify all returned entries have single_ref references + console.log(`Found ${result.entries?.length} entries with referenceIn authors`); + + // Verify all returned entries have authors references result.entries.forEach((entry: any) => { - if (entry.single_ref) { - expect(Array.isArray(entry.single_ref)).toBe(true); - // Verify references are resolved - entry.single_ref.forEach((author: any) => { + if (entry.authors) { + expect(Array.isArray(entry.authors)).toBe(true); + // Verify authors are resolved + entry.authors.forEach((author: any) => { expect(author.uid).toBeDefined(); expect(author._content_type_uid).toBe('author'); }); @@ -472,20 +472,20 @@ describe('Query Operators - Comprehensive Coverage', () => { .contentType(COMPLEX_CT) .entry() .query() - .referenceNotIn('single_ref', excludeAuthorQuery) + .referenceNotIn('authors', excludeAuthorQuery) .find(); expect(result).toBeDefined(); - + if (result.entries && result.entries?.length > 0) { - console.log(`Found ${result.entries?.length} entries with referenceNotIn single_ref`); - + console.log(`Found ${result.entries?.length} entries with referenceNotIn authors`); + // Verify all returned entries don't have excluded author references result.entries.forEach((entry: any) => { - if (entry.single_ref) { - expect(Array.isArray(entry.single_ref)).toBe(true); + if (entry.authors) { + expect(Array.isArray(entry.authors)).toBe(true); // Verify no excluded author UID is referenced - entry.single_ref.forEach((author: any) => { + entry.authors.forEach((author: any) => { expect(author.uid).not.toBe('non_existent_author_uid'); }); } diff --git a/test/api/stack-operations-comprehensive.spec.ts b/test/api/stack-operations-comprehensive.spec.ts index 3dccf04e..fa41fe41 100644 --- a/test/api/stack-operations-comprehensive.spec.ts +++ b/test/api/stack-operations-comprehensive.spec.ts @@ -380,8 +380,8 @@ describe('Stack Operations - Comprehensive Coverage', () => { .find(); expect(taxonomyResult).toBeDefined(); - expect(taxonomyResult.taxonomies).toBeDefined(); - expect(Array.isArray(taxonomyResult.taxonomies)).toBe(true); + expect(taxonomyResult.entries).toBeDefined(); + expect(Array.isArray(taxonomyResult.entries)).toBe(true); // Then get last activities const activitiesResult = await (stack as any).getLastActivities(); @@ -394,7 +394,7 @@ describe('Stack Operations - Comprehensive Coverage', () => { expect(activitiesResult).toBeDefined(); if (activitiesResult.content_types) { expect(Array.isArray(activitiesResult.content_types)).toBe(true); - console.log(`Taxonomy operations: ${taxonomyResult.taxonomies?.length} taxonomies`); + console.log(`Taxonomy operations: ${taxonomyResult.entries?.length} taxonomies`); console.log(`Last activities: ${activitiesResult.content_types.length} content types`); } } catch (error: any) { diff --git a/test/api/sync-operations-comprehensive.spec.ts b/test/api/sync-operations-comprehensive.spec.ts index 3210ba48..a2739b35 100644 --- a/test/api/sync-operations-comprehensive.spec.ts +++ b/test/api/sync-operations-comprehensive.spec.ts @@ -83,10 +83,8 @@ describe('Sync Operations Comprehensive Tests', () => { expect(result).toBeDefined(); expect(result.items).toBeDefined(); expect(Array.isArray(result.items)).toBe(true); - // Initial sync over a large stack paginates: first page returns a pagination_token, - // and sync_token only arrives on the final page. Accept either. - expect(result.sync_token ?? result.pagination_token).toBeDefined(); - + expect(result.sync_token).toBeDefined(); + console.log('Initial sync (all content types):', { duration: `${duration}ms`, entriesCount: result.items.length, @@ -174,16 +172,7 @@ describe('Sync Operations Comprehensive Tests', () => { expect(result.items).toBeDefined(); expect(Array.isArray(result.items)).toBe(true); expect(result.sync_token).toBeDefined(); - expect(typeof result.sync_token).toBe('string'); - // A delta sync always returns a usable token. If there were NO changes since the - // initial sync, the token is unchanged; if the sync log has intervening events - // (e.g. prior publish/unpublish), the delta returns those change items and a NEW - // token. Assert the correct behaviour for each case instead of a blanket equality. - if (result.items.length === 0) { - expect(result.sync_token).toBe(initialSyncToken); - } else { - expect(result.sync_token).not.toBe(initialSyncToken); - } + expect(result.sync_token).toBe(initialSyncToken); console.log('Delta sync completed:', { duration: `${duration}ms`, @@ -286,8 +275,8 @@ describe('Sync Operations Comprehensive Tests', () => { syncToken: result.sync_token }); - // The Sync API returns up to one page (max 100 items); it does not honor an arbitrary small limit. - expect(result.items.length).toBeLessThanOrEqual(100); + // Should respect the limit + expect(result.items.length).toBeLessThanOrEqual(5); }); it('should handle sync pagination with skip', async () => { @@ -491,10 +480,9 @@ describe('Sync Operations Comprehensive Tests', () => { ratio: initialTime / deltaTime }); - // Delta sync should be reasonably fast, but wall-clock timing over a live network is noisy - // (initial sync warms caches, delta can hit a cold shard). Use a generous tolerance so this - // catches gross regressions without flaking on normal variance. - const maxAllowedTime = Math.max(initialTime * 3, 3000); + // Delta sync should be reasonably fast (allow 2x tolerance OR absolute 100ms threshold) + // This accounts for network variability while catching real performance regressions + const maxAllowedTime = Math.max(initialTime * 2, 100); expect(deltaTime).toBeLessThanOrEqual(maxAllowedTime); }); @@ -657,14 +645,7 @@ describe('Sync Operations Comprehensive Tests', () => { expect(deltaResult.sync_token).toBeDefined(); expect(typeof deltaResult.sync_token).toBe('string'); - // The token is stable only when the delta finds no changes; if the sync log has - // intervening events the token advances (returning those change items). Assert the - // correct behaviour for each case rather than assuming a pristine event log. - if (deltaResult.items.length === 0) { - expect(deltaResult.sync_token).toBe(initialResult.sync_token); - } else { - expect(deltaResult.sync_token).not.toBe(initialResult.sync_token); - } + expect(deltaResult.sync_token).toBe(initialResult.sync_token); console.log('Sync token consistency:', { initialToken: initialResult.sync_token, diff --git a/test/api/taxonomy.spec.ts b/test/api/taxonomy.spec.ts deleted file mode 100644 index a7178073..00000000 --- a/test/api/taxonomy.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable no-console */ -/* eslint-disable promise/always-return */ -import { stackInstance } from '../utils/stack-instance'; -import { TTaxonomies, TTaxonomy } from './types'; -import dotenv from 'dotenv'; -import { TaxonomyQuery } from '../../src/query/taxonomy-query'; -import { Taxonomy } from '../../src/taxonomy'; - -dotenv.config() -const countryUsa = process.env.TAX_COUNTRY_USA || 'usa' -const locale = process.env.TAX_LOCALE || 'en-us' -const stack = stackInstance(); -describe('Taxonomy API test cases', () => { - it('should give taxonomies when taxonomies method is called', async () => { - const result = await makeTaxonomies().find(); - expect(result).toBeDefined(); - }); - - it('should give a single taxonomy when taxonomy method is called with taxonomyUid', async () => { - const result = await makeTaxonomy(countryUsa).fetch(); - expect(result).toBeDefined(); - }); - - it('should give a localized taxonomy when locale is set via param()', async () => { - const result = await makeTaxonomy(countryUsa).param('locale', locale).fetch(); - expect(result).toBeDefined(); - if (result.publish_details) { - expect(result.publish_details.locale).toBeDefined(); - } - }); - - it('should give a taxonomy with locale fallback when includeFallback is chained', async () => { - const result = await makeTaxonomy(countryUsa).param('locale', locale).includeFallback().fetch(); - expect(result).toBeDefined(); - }); - - it('should give a localized taxonomy when locale is set via param()', async () => { - const result = await makeTaxonomy('gadgets').param('locale', 'fr-fr').fetch(); - expect(result).toBeDefined(); - }); -}); - -describe('Taxonomy API test cases - gadgets', () => { - it('should fetch gadgets taxonomy in en-us (master locale)', async () => { - const result = await makeTaxonomy('gadgets').fetch(); - expect(result).toBeDefined(); - expect(result.uid).toBe('gadgets'); - }); - - it('should fetch gadgets taxonomy in fr-fr locale via param()', async () => { - const result = await makeTaxonomy('gadgets').param('locale', 'fr-fr').fetch(); - expect(result).toBeDefined(); - expect(result.uid).toBe('gadgets'); - expect(result.locale).toBe('fr-fr'); - }); - - it('should return gadgets in the taxonomies list', async () => { - const result = await makeTaxonomies().find(); - expect(result).toBeDefined(); - expect(result.taxonomies).toBeDefined(); - const gadgets = result.taxonomies!.find((t: any) => t.uid === 'gadgets'); - expect(gadgets).toBeDefined(); - }); -}); - -function makeTaxonomies(): TaxonomyQuery { - const taxonomies = stack.taxonomy(); - - return taxonomies; -} - -function makeTaxonomy(taxonomyUid: string): Taxonomy { - const taxonomy = stack.taxonomy(taxonomyUid); - return taxonomy; -} \ No newline at end of file diff --git a/test/api/term-query.spec.ts b/test/api/term-query.spec.ts deleted file mode 100644 index ce267bb5..00000000 --- a/test/api/term-query.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { TermQuery } from "../../src/query/term-query"; -import { stackInstance } from "../utils/stack-instance"; -import { TTerm } from "./types"; -import dotenv from 'dotenv'; - -dotenv.config() -const stack = stackInstance(); -const countryUsa = process.env.TAX_COUNTRY_USA || 'usa' -const locale = process.env.TAX_LOCALE || 'en-us' - -describe("Terms Query API test cases", () => { - it("should check for terms is defined", async () => { - const result = await makeTerms(countryUsa).find(); - if (result.terms) { - expect(result.terms).toBeDefined(); - expect(result.terms[0].taxonomy_uid).toBeDefined(); - expect(result.terms[0].uid).toBeDefined(); - expect(result.terms[0].created_by).toBeDefined(); - expect(result.terms[0].updated_by).toBeDefined(); - } - }); - - it("should return terms for the requested locale when locale() is chained", async () => { - const result = await makeTerms(countryUsa).locale(locale).find(); - if (result.terms && result.terms.length) { - expect(result.terms).toBeDefined(); - result.terms.forEach((term) => { - if (term.publish_details) { - expect(term.publish_details.locale).toEqual(locale); - } - }); - } - }); - - it("should return terms with locale fallback when includeFallback() is chained", async () => { - const result = await makeTerms(countryUsa).locale(locale).includeFallback().find(); - if (result.terms) { - expect(result.terms).toBeDefined(); - } - }); - - it("should return terms for given locale when locale() is chained", async () => { - const result = await makeTerms("gadgets").locale("fr-fr").find(); - expect(result).toBeDefined(); - }); - - it("should return terms with fallback when includeFallback() is chained", async () => { - const result = await makeTerms("gadgets").includeFallback().find(); - expect(result).toBeDefined(); - }); - - it("should return localized terms with fallback when locale() and includeFallback() are chained", async () => { - const result = await makeTerms("gadgets").locale("fr-fr").includeFallback().find(); - expect(result).toBeDefined(); - }); -}); - -function makeTerms(taxonomyUid = ""): TermQuery { - const terms = stack.taxonomy(taxonomyUid).term(); - return terms; -} - -describe("Term Query API test cases - gadgets taxonomy", () => { - // Case 1: locale=en-us, include_fallback=false - // Returns 5 terms: tablet, laptop, smartwatch, smartphone, headphone — all en-us - it("should return all 5 en-us terms when locale is en-us and includeFallback is not set", async () => { - const result = await stack.taxonomy("gadgets").term().locale("en-us").find(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBe(5); - const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); - expect(byUid["tablet"].name).toBe("Tablet"); - expect(byUid["laptop"].name).toBe("Laptop"); - expect(byUid["smartwatch"].name).toBe("Smartwatch"); - expect(byUid["smartphone"].name).toBe("Smartphone"); - expect(byUid["headphone"].name).toBe("Headphone"); - result.terms!.forEach((t: any) => expect(t.locale).toBe("en-us")); - }); - - // Case 2: locale=en-us, include_fallback=true - // Returns same 5 terms — all en-us (no change since en-us is master) - it("should return all 5 en-us terms when locale is en-us and includeFallback is true", async () => { - const result = await stack.taxonomy("gadgets").term().locale("en-us").includeFallback().find(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBe(5); - const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); - expect(byUid["tablet"].name).toBe("Tablet"); - expect(byUid["laptop"].name).toBe("Laptop"); - expect(byUid["smartwatch"].name).toBe("Smartwatch"); - expect(byUid["smartphone"].name).toBe("Smartphone"); - expect(byUid["headphone"].name).toBe("Headphone"); - result.terms!.forEach((t: any) => expect(t.locale).toBe("en-us")); - }); - - // Case 3: locale=fr-fr, include_fallback=false - // Returns 3 fr-fr terms only — tablet and laptop have no fr-fr translation so they are excluded - it("should return only 3 fr-fr localized terms when locale is fr-fr and includeFallback is false", async () => { - const result = await stack.taxonomy("gadgets").term().locale("fr-fr").find(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBe(3); - const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); - expect(byUid["headphone"].name).toBe("Headphone-fr"); - expect(byUid["smartphone"].name).toBe("Smartphone-fr"); - expect(byUid["smartwatch"].name).toBe("Smartwatch-fr"); - expect(byUid["tablet"]).toBeUndefined(); - expect(byUid["laptop"]).toBeUndefined(); - result.terms!.forEach((t: any) => expect(t.locale).toBe("fr-fr")); - }); - - // Case 4: locale=fr-fr, include_fallback=true - // Returns 5 terms: 3 in fr-fr + 2 fallback to en-us (tablet, laptop) - it("should return 5 terms with fr-fr terms and en-us fallback when locale is fr-fr and includeFallback is true", async () => { - const result = await stack.taxonomy("gadgets").term().locale("fr-fr").includeFallback().find(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBe(5); - const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); - expect(byUid["headphone"].name).toBe("Headphone-fr"); - expect(byUid["headphone"].locale).toBe("fr-fr"); - expect(byUid["smartphone"].name).toBe("Smartphone-fr"); - expect(byUid["smartphone"].locale).toBe("fr-fr"); - expect(byUid["smartwatch"].name).toBe("Smartwatch-fr"); - expect(byUid["smartwatch"].locale).toBe("fr-fr"); - expect(byUid["tablet"].name).toBe("Tablet"); - expect(byUid["tablet"].locale).toBe("en-us"); - expect(byUid["laptop"].name).toBe("Laptop"); - expect(byUid["laptop"].locale).toBe("en-us"); - }); -}); diff --git a/test/api/term.spec.ts b/test/api/term.spec.ts deleted file mode 100644 index dcd92c83..00000000 --- a/test/api/term.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Term } from "../../src/taxonomy/term"; -import { stackInstance } from "../utils/stack-instance"; -import { TTerm, TTerms } from "./types"; -import dotenv from 'dotenv'; - -dotenv.config() -const countryUsa = process.env.TAX_COUNTRY_USA || 'usa' -const locale = process.env.TAX_LOCALE || 'en-us' -const stack = stackInstance(); - -describe("Terms API test cases", () => { - it("should get a term by uid", async () => { - const result = await makeTerms("texas").fetch(); - expect(result).toBeDefined(); - expect(result.taxonomy_uid).toBeDefined(); - expect(result.uid).toBeDefined(); - expect(result.created_by).toBeDefined(); - expect(result.updated_by).toBeDefined(); - }); - - it("should get a localized term when locale is set via param()", async () => { - const result = await makeTerms("texas").param('locale', locale).fetch(); - expect(result).toBeDefined(); - if (result.publish_details) { - expect(result.publish_details.locale).toBeDefined(); - } - }); - - it("should get a term with locale fallback when includeFallback is chained", async () => { - const result = await makeTerms("texas").param('locale', locale).includeFallback().fetch(); - expect(result).toBeDefined(); - }); - - it("should get a localized term when locale is set via param()", async () => { - const result = await stack.taxonomy("gadgets").term("smartphone").param('locale', 'fr-fr').fetch(); - expect(result).toBeDefined(); - }); - - it("should get locales for a term", async () => { - const result = await makeTerms("texas").locales(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms![0].name).toBeDefined(); - }); - - it("should get ancestors for a term", async () => { - const result = await makeTerms("houston").ancestors(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms![0].name).toBeDefined(); - }); - - it("should get descendants for a term", async () => { - const result = await makeTerms("texas").descendants(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms![0].name).toBeDefined(); - }); -}); - -function makeTerms(termUid = ""): Term { - const terms = stack.taxonomy(countryUsa).term(termUid); - return terms; -} - -describe("Terms API test cases - gadgets taxonomy", () => { - it("should fetch a term from gadgets taxonomy", async () => { - const result = await stack.taxonomy("gadgets").term("smartphone").fetch(); - expect(result).toBeDefined(); - expect(result.uid).toBe("smartphone"); - expect(result.taxonomy_uid).toBe("gadgets"); - }); - - it("should fetch smartphone term from gadgets in fr-fr locale via param()", async () => { - const result = await stack.taxonomy("gadgets").term("smartphone").param('locale', 'fr-fr').fetch(); - expect(result).toBeDefined(); - expect(result.uid).toBe("smartphone"); - expect(result.locale).toBe("fr-fr"); - expect((result as any).name).toBe("Smartphone-fr"); - }); - - it("should fetch all locales for a gadgets term", async () => { - const result = await stack.taxonomy("gadgets").term("smartphone").locales(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBeGreaterThan(0); - const locales = result.terms!.map((t: any) => t.locale); - expect(locales).toContain("en-us"); - expect(locales).toContain("fr-fr"); - }); - - it("should return empty ancestors for a root-level term in gadgets", async () => { - const result = await stack.taxonomy("gadgets").term("smartphone").ancestors(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBe(0); - }); - - it("should return empty descendants for a leaf term in gadgets", async () => { - const result = await stack.taxonomy("gadgets").term("smartphone").descendants(); - expect(result).toBeDefined(); - expect(result.terms).toBeDefined(); - expect(result.terms!.length).toBe(0); - }); -}); diff --git a/test/api/types.ts b/test/api/types.ts index d5083fba..776e3b2c 100644 --- a/test/api/types.ts +++ b/test/api/types.ts @@ -86,42 +86,3 @@ export interface TContentType { export interface TContentTypes { content_types: TContentType[]; } - -export interface TTaxonomies { - taxonomies: TTaxonomy[]; -} - -export interface TTaxonomy { - uid: string; - name: string; - locale?: string; - description?: string; - terms_count?: number; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string; - type?: string; - publish_details?: PublishDetails; -} - -export interface TTerms { - terms?: TTerm[]; -} - -export interface TTerm { - taxonomy_uid: string; - uid: string; - locale?: string; - ancestors?: TTerm[]; - name: string; - created_by: string; - created_at: string; - updated_by: string; - updated_at: string; - children_count?: number; - depth?: number; - parent_uid?: string | null; - publish_details?: PublishDetails; - terms?: TTerm[]; -} \ No newline at end of file diff --git a/test/bundlers/run-with-report.sh b/test/bundlers/run-with-report.sh index 4092b362..6fd3ba9e 100755 --- a/test/bundlers/run-with-report.sh +++ b/test/bundlers/run-with-report.sh @@ -43,26 +43,10 @@ run_bundler_test() { if [ -d "$bundler_dir" ]; then cd "$bundler_dir" - # Install dependencies. - # Regenerate the lockfile each run: these apps depend on the SDK via `file:../../..`, - # and a stale package-lock.json triggers npm's "Cannot read properties of undefined - # (reading 'extraneous')" bug. Don't suppress errors — surface them so a real install - # failure is visible instead of a silent exit (the script runs with `set -e`). + # Install dependencies echo "📦 Installing dependencies..." - rm -f package-lock.json - if ! npm install --no-audit --no-fund > /tmp/${bundler}-install.log 2>&1; then - echo "❌ Install failed:" - tail -20 "/tmp/${bundler}-install.log" - tests=$((tests + 1)) - failed=$((failed + 1)) - # Record the failure for this bundler and move on to the next one. - BUNDLERS+=("{\"bundler\":\"$bundler\",\"total\":1,\"passed\":0,\"failed\":1,\"duration\":0,\"success\":false}") - TOTAL_TESTS=$((TOTAL_TESTS + 1)) - FAILED_TESTS=$((FAILED_TESTS + 1)) - cd "$SCRIPT_DIR" - return 0 - fi - + npm install --silent > /dev/null 2>&1 + # Build echo "🔨 Building..." if npm run build > /dev/null 2>&1; then @@ -78,19 +62,14 @@ run_bundler_test() { # Run tests echo "🧪 Running tests..." if npm test 2>&1 | tee /tmp/${bundler}-test-output.txt; then - # Count passing tests from output. grep -c already prints "0" on no match; the old - # `|| echo N` appended a second line ("0\nN") and broke the later $(( )) arithmetic. - local test_count=$(grep -c "✓" /tmp/${bundler}-test-output.txt 2>/dev/null || true) - test_count=${test_count:-0} + # Count passing tests from output + local test_count=$(grep -c "✓" /tmp/${bundler}-test-output.txt || echo "0") tests=$((tests + test_count)) passed=$((passed + test_count)) echo "✅ Tests passed ($test_count tests)" else - local test_count=$(grep -c "✓\|✗" /tmp/${bundler}-test-output.txt 2>/dev/null || true) - test_count=${test_count:-0} - [ "$test_count" -eq 0 ] && test_count=1 - local pass_count=$(grep -c "✓" /tmp/${bundler}-test-output.txt 2>/dev/null || true) - pass_count=${pass_count:-0} + local test_count=$(grep -c "✓\|✗" /tmp/${bundler}-test-output.txt || echo "1") + local pass_count=$(grep -c "✓" /tmp/${bundler}-test-output.txt || echo "0") local fail_count=$((test_count - pass_count)) tests=$((tests + test_count)) passed=$((passed + pass_count)) diff --git a/test/bundlers/webpack-app/package.json b/test/bundlers/webpack-app/package.json index 9f910078..53bbab4d 100644 --- a/test/bundlers/webpack-app/package.json +++ b/test/bundlers/webpack-app/package.json @@ -11,7 +11,8 @@ "@contentstack/delivery-sdk": "file:../../.." }, "devDependencies": { - "webpack": "5.108.0", + "webpack": "^5.89.0", "webpack-cli": "^5.1.4" } } + diff --git a/test/reporting/rich-html-reporter.cjs b/test/reporting/rich-html-reporter.cjs deleted file mode 100644 index 19403332..00000000 --- a/test/reporting/rich-html-reporter.cjs +++ /dev/null @@ -1,265 +0,0 @@ -/** - * Rich single-file HTML reporter for the delivery-SDK API tests. - * - * Renders one self-contained, timestamped HTML file with per-test "Additional - * Test Context" shown INLINE (SDK method, API request+status, copy-paste cURL, - * request/response headers, response body) — modeled on the CMA SDK's report. - * - * HTTP context comes from test-results/http-captures.jsonl, appended per test by - * jest.setup.ts when ENABLE_HTTP_CAPTURE=true. Output: reports/api-report-.html - * (filename timestamped, no nested folder). The absolute path is printed at run end. - */ -const fs = require('fs'); -const path = require('path'); - -function esc(s) { - return String(s === undefined || s === null ? '' : s) - .replace(/&/g, '&') - .replace(//g, '>'); -} - -function pretty(v) { - if (v === undefined || v === null) return ''; - if (typeof v === 'string') return v; - try { - return JSON.stringify(v, null, 2); - } catch { - return String(v); - } -} - -class RichHtmlReporter { - constructor(globalConfig, options) { - this._options = options || {}; - this._suites = []; - this._capturesFile = path.resolve(process.cwd(), 'test-results', 'http-captures.jsonl'); - } - - onRunStart() { - // Start each run with a clean capture sidecar. - try { - if (fs.existsSync(this._capturesFile)) fs.unlinkSync(this._capturesFile); - } catch { - /* ignore */ - } - } - - onTestResult(_test, testResult) { - this._suites.push({ - file: testResult.testFilePath, - tests: (testResult.testResults || []).map((t) => ({ - fullName: t.fullName, - title: t.title, - ancestorTitles: t.ancestorTitles || [], - status: t.status, - failureMessages: t.failureMessages || [], - duration: t.duration || 0, - })), - }); - } - - _loadCaptures() { - const byKey = {}; - const byName = {}; - try { - if (!fs.existsSync(this._capturesFile)) return { byKey, byName }; - const lines = fs.readFileSync(this._capturesFile, 'utf8').split('\n').filter(Boolean); - for (const line of lines) { - try { - const r = JSON.parse(line); - const rec = { capture: r.capture || null, assertions: r.assertions || [] }; - byKey[`${r.testPath}::${r.testName}`] = rec; - byName[r.testName] = rec; // fallback if testPath differs - } catch { - /* skip bad line */ - } - } - } catch { - /* ignore */ - } - return { byKey, byName }; - } - - onRunComplete(_contexts, results) { - const { byKey, byName } = this._loadCaptures(); - - // Fixed output path (default matches the path the GoCD pipelines already link to). - const outFile = path.resolve( - process.cwd(), - this._options.outputPath || 'reports/contentstack-delivery/html/index.html' - ); - const outDir = path.dirname(outFile); - if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); - - const displayTs = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; - const html = this._render(results, byKey, byName, displayTs); - fs.writeFileSync(outFile, html); - - // eslint-disable-next-line no-console - console.log(`\n\u{1F4C4} Rich API test report: ${outFile}\n`); - } - - _renderContext(rec, test) { - const cap = rec && rec.capture; - const assertions = (rec && rec.assertions) || []; - const passed = test.status === 'passed'; - const rows = []; - rows.push( - `
✅ Test Result:
${passed ? 'PASSED' : test.status.toUpperCase()}
` - ); - - if (assertions.length) { - // Human-friendly "expected" text for argument-less matchers (which have no expected value). - const NOARG = { - toBeDefined: 'to be defined (not undefined)', - toBeUndefined: 'to be undefined', - toBeNull: 'to be null', - toBeTruthy: 'to be truthy', - toBeFalsy: 'to be falsy', - toBeNaN: 'to be NaN', - toHaveBeenCalled: 'to have been called', - }; - const items = assertions - .map((a) => { - const name = `${a.isNot ? 'not.' : ''}${a.matcher}`; - let exp = a.expected; - if (exp === '') exp = NOARG[a.matcher] || '— (argument-less matcher)'; - return ( - `
${a.passed ? '✓' : '✗'} ${esc(name)}
` + - `
Expected:
${esc(exp)}
` + - `
Actual:
${esc(a.actual)}
` - ); - }) - .join(''); - const npass = assertions.filter((a) => a.passed).length; - rows.push( - `
📊 Assertions Verified (Expected vs Actual):
${npass}/${assertions.length} passed
${items}
` - ); - } - - if (!passed && test.failureMessages.length) { - const msg = test.failureMessages.join('\n\n').replace(/\[[0-9;]*m/g, ''); // strip ANSI - rows.push( - `
❌ Expected vs Actual (failure):
${esc(msg)}
` - ); - } - - if (cap) { - rows.push(`
\u{1F4E6} SDK Method Tested:
${esc(cap.sdkMethod)}
`); - rows.push( - `
\u{1F4E1} API Request:
${esc(`${cap.method} ${cap.url} [${cap.status == null ? 'no response' : cap.status}]`)}
` - ); - rows.push(`
\u{1F4CB} cURL Command (copy-paste ready):
${esc(cap.curl)}
`); - if (cap.requestHeaders && Object.keys(cap.requestHeaders).length) { - rows.push(`
\u{1F4E4} Request Headers:
${esc(pretty(cap.requestHeaders))}
`); - } - if (cap.responseHeaders && Object.keys(cap.responseHeaders).length) { - rows.push(`
\u{1F4E5} Response Headers:
${esc(pretty(cap.responseHeaders))}
`); - } - if (cap.responseBody !== undefined && cap.responseBody !== null && cap.responseBody !== '') { - rows.push(`
\u{1F4E5} Response Body:
${esc(pretty(cap.responseBody))}
`); - } - if (cap.duration != null) { - rows.push(`
⏱ Duration:
${cap.duration}ms
`); - } - } else { - rows.push(`
No HTTP call captured for this test.
`); - } - return `
Additional Test Context
${rows.join('')}
`; - } - - _render(results, byKey, byName, ts) { - const total = results.numTotalTests || 0; - const passed = results.numPassedTests || 0; - const failed = results.numFailedTests || 0; - const pending = (results.numPendingTests || 0) + (results.numTodoTests || 0); - const suitesFailed = results.numFailedTestSuites || 0; - const host = process.env.HOST || ''; - const env = process.env.ENVIRONMENT || ''; - - const suiteHtml = this._suites - .sort((a, b) => a.file.localeCompare(b.file)) - .map((s) => { - const rel = s.file.replace(process.cwd() + path.sep, ''); - const sPass = s.tests.filter((t) => t.status === 'passed').length; - const sFail = s.tests.filter((t) => t.status === 'failed').length; - const sSkip = s.tests.length - sPass - sFail; - const testsHtml = s.tests - .map((t) => { - const rec = byKey[`${s.file}::${t.fullName}`] || byName[t.fullName]; - const icon = t.status === 'passed' ? '✅' : t.status === 'failed' ? '❌' : '⚪'; - const cls = t.status === 'passed' ? 'passed' : t.status === 'failed' ? 'failed' : 'skipped'; - const ancestry = t.ancestorTitles.length ? `${esc(t.ancestorTitles.join(' › '))} ` : ''; - return `
- ${icon}${ancestry}${esc(t.title)}${t.duration}ms - ${this._renderContext(rec, t)} -
`; - }) - .join('\n'); - return `
-
- ${esc(rel)} - ${sPass} passed${sFail ? `${sFail} failed` : ''}${sSkip ? `${sSkip} skipped` : ''} - -
${testsHtml}
-
-
`; - }) - .join('\n'); - - return ` - -TS-CDA API Test Report — ${esc(ts)} - -
-

TS-CDA API Test Report

-
${esc(ts)}${host ? ' · host: ' + esc(host) : ''}${env ? ' · env: ' + esc(env) : ''}
-
-
${total}
Total
-
${passed}
Passed
-
${failed}
Failed
- -
${this._suites.length}
Suites${suitesFailed ? ' (' + suitesFailed + ' ❌)' : ''}
-
- ${suiteHtml} -
`; - } -} - -module.exports = RichHtmlReporter; diff --git a/test/unit/base-query.spec.ts b/test/unit/base-query.spec.ts index cf7fb1d5..3cc3575f 100644 --- a/test/unit/base-query.spec.ts +++ b/test/unit/base-query.spec.ts @@ -158,17 +158,12 @@ class TestableBaseQuery extends BaseQuery { this._urlPath = urlPath; } this._variants = ''; - this._variantsBranch = ''; } setVariants(variants: string) { this._variants = variants; } - setVariantsBranch(branch: string) { - this._variantsBranch = branch; - } - setParameters(params: any) { this._parameters = params; } @@ -309,18 +304,6 @@ describe('BaseQuery find method', () => { expect(result).toEqual(entryFindMock); }); - it('should call find with variant and branch headers when branch is set', async () => { - mockClient.onGet('/content_types/test_uid/entries').reply((config) => { - expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); - expect(config.headers?.branch).toBe('branch_name'); - return [200, entryFindMock]; - }); - - query.setVariants('variant1,variant2'); - query.setVariantsBranch('branch_name'); - await query.find(); - }); - it('should call find with variants header when variants are set', async () => { mockClient.onGet('/content_types/test_uid/entries').reply((config) => { expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); diff --git a/test/unit/entries.spec.ts b/test/unit/entries.spec.ts index ddcf1e80..461a9d66 100644 --- a/test/unit/entries.spec.ts +++ b/test/unit/entries.spec.ts @@ -275,13 +275,6 @@ describe('Variants test', () => { testVariantObj.variants([]); expect(testVariantObj.getVariants()).toBe(''); }); - - it('should set branch when branch name is provided', () => { - const testVariantObj = new TestVariants(client); - testVariantObj.variants(['variant1', 'variant2'], 'branch_name'); - expect(testVariantObj.getVariants()).toBe('variant1,variant2'); - expect(testVariantObj['_variantsBranch']).toBe('branch_name'); - }); }); describe('Find with encode and variants', () => { @@ -318,28 +311,6 @@ describe('Find with encode and variants', () => { await entry.find(); }); - it('should call find with variant and branch headers when branch is set', async () => { - mockClient.onGet('/content_types/contentTypeUid/entries').reply((config) => { - expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); - expect(config.headers?.branch).toBe('branch_name'); - return [200, entryFindMock]; - }); - - entry.variants(['variant1', 'variant2'], 'branch_name'); - await entry.find(); - }); - - it('should pass branch to query find when variants include branch', async () => { - mockClient.onGet('/content_types/contentTypeUid/entries').reply((config) => { - expect(config.headers?.['x-cs-variant-uid']).toBe('variant1'); - expect(config.headers?.branch).toBe('branch_name'); - return [200, entryFindMock]; - }); - - entry.variants('variant1', 'branch_name'); - await entry.query().find(); - }); - it('should handle find with both encode and variants', async () => { mockClient.onGet('/content_types/contentTypeUid/entries').reply((config) => { expect(config.headers?.['x-cs-variant-uid']).toBe('test-variant'); diff --git a/test/unit/entry.spec.ts b/test/unit/entry.spec.ts index 0abd4595..65730aec 100644 --- a/test/unit/entry.spec.ts +++ b/test/unit/entry.spec.ts @@ -200,13 +200,6 @@ describe('Variants test', () => { testVariantObj.variants([]); expect(testVariantObj.getVariants()).toBe(''); }); - - it('should set branch when branch name is provided', () => { - const testVariantObj = new TestVariants(client); - testVariantObj.variants('variant1', 'branch_name'); - expect(testVariantObj.getVariants()).toBe('variant1'); - expect(testVariantObj['_variantsBranch']).toBe('branch_name'); - }); }); describe('Fetch with variants', () => { @@ -236,32 +229,6 @@ describe('Fetch with variants', () => { expect(result).toEqual(entryFetchMock.entry); }); - it('should call fetch with variant and branch headers when branch is set', async () => { - mockClient.onGet('/content_types/contentTypeUid/entries/entryUid').reply((config) => { - expect(config.headers?.['x-cs-variant-uid']).toBe('variant1'); - expect(config.headers?.branch).toBe('branch_name'); - return [200, entryFetchMock]; - }); - - entry.variants('variant1', 'branch_name'); - const result = await entry.fetch(); - - expect(result).toEqual(entryFetchMock.entry); - }); - - it('should call fetch with variant and branch headers for multiple variants', async () => { - mockClient.onGet('/content_types/contentTypeUid/entries/entryUid').reply((config) => { - expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2'); - expect(config.headers?.branch).toBe('branch_name'); - return [200, entryFetchMock]; - }); - - entry.variants(['variant1', 'variant2'], 'branch_name'); - const result = await entry.fetch(); - - expect(result).toEqual(entryFetchMock.entry); - }); - it('should call fetch without variant header when variants are not set', async () => { mockClient.onGet('/content_types/contentTypeUid/entries/entryUid').reply((config) => { expect(config.headers?.['x-cs-variant-uid']).toBeUndefined(); diff --git a/test/unit/taxonomy.spec.ts b/test/unit/taxonomy.spec.ts deleted file mode 100644 index 8517edc1..00000000 --- a/test/unit/taxonomy.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { TaxonomyQuery } from '../../src/query/taxonomy-query'; -import { Taxonomy } from '../../src/taxonomy'; -import { AxiosInstance, httpClient } from '@contentstack/core'; -import MockAdapter from 'axios-mock-adapter'; -import { taxonomyFindResponseDataMock, taxonomyLocalizedFetchMock } from '../utils/mocks'; -import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; -import { Term } from '../../src/taxonomy/term'; -import { TermQuery } from '../../src/query/term-query'; - -describe('ta class', () => { - let taxonomies: TaxonomyQuery; - let taxonomy: Taxonomy; - let client: AxiosInstance; - let mockClient: MockAdapter; - - beforeAll(() => { - client = httpClient(MOCK_CLIENT_OPTIONS); - mockClient = new MockAdapter(client as any); - }); - - beforeEach(() => { - taxonomies = new TaxonomyQuery(client); - taxonomy = new Taxonomy(client, 'taxonomy_testing'); - }); - - it('should give term instance when term method is called with termUid', () => { - const query = taxonomy.term('termUid'); - expect(query).toBeInstanceOf(Term); - }); - - it('should give term query instance when term method is called without termUid', () => { - const query = taxonomy.term() - expect(query).toBeInstanceOf(TermQuery); - }); - - it('should return all taxonomies in the response data when successful', async () => { - mockClient.onGet('/taxonomies').reply(200, taxonomyFindResponseDataMock); - const response = await taxonomies.find(); - expect(response).toEqual(taxonomyFindResponseDataMock); - }); - - it('should return single taxonomy in the response data when successful', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing').reply(200, taxonomyFindResponseDataMock.taxonomies[0]); - const response = await taxonomy.fetch(); - expect(response).toEqual(taxonomyFindResponseDataMock.taxonomies[0]); - }); - - it('should send include and arbitrary params on fetch() when chained', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing').reply((config) => { - expect(config.params).toEqual(expect.objectContaining({ include_fallback: 'true', include_branch: 'true', locale: 'fr-fr' })); - return [200, taxonomyFindResponseDataMock.taxonomies[0]]; - }); - - await taxonomy.includeFallback().includeBranch().param('locale', 'fr-fr').fetch(); - }); - - it('should return localized taxonomy when param is used to set locale', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing').reply(200, taxonomyLocalizedFetchMock); - const response = await taxonomy.param('locale', 'hi-in').fetch(); - expect(response).toEqual(taxonomyLocalizedFetchMock.taxonomy); - }); -}); diff --git a/test/unit/term-query.spec.ts b/test/unit/term-query.spec.ts deleted file mode 100644 index e6de3d11..00000000 --- a/test/unit/term-query.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { TermQuery } from '../../src/query/term-query'; -import { AxiosInstance, httpClient } from '@contentstack/core'; -import MockAdapter from 'axios-mock-adapter'; -import { termQueryFindResponseDataMock, termQueryLocalizedFindMock } from '../utils/mocks'; -import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; - -describe('TermQuery class', () => { - let termQuery: TermQuery; - let client: AxiosInstance; - let mockClient: MockAdapter; - - beforeAll(() => { - client = httpClient(MOCK_CLIENT_OPTIONS); - mockClient = new MockAdapter(client as any); - }); - - beforeEach(() => { - termQuery = new TermQuery(client, 'taxonomy_testing'); - }); - - it('should return response data when successful', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryFindResponseDataMock); - const response = await termQuery.find(); - expect(response).toEqual(termQueryFindResponseDataMock); - }); - - it('should send pagination and include params when chained on find()', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply((config) => { - expect(config.params).toEqual(expect.objectContaining({ - depth: 2, - skip: 10, - limit: 5, - include_count: 'true', - include_fallback: 'true', - include_branch: 'true', - })); - return [200, termQueryFindResponseDataMock]; - }); - - await termQuery - .depth(2) - .skip(10) - .limit(5) - .includeCount() - .includeFallback() - .includeBranch() - .find(); - }); - - it('should send arbitrary params added via param() and addParams() on find()', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply((config) => { - expect(config.params).toEqual(expect.objectContaining({ locale: 'fr-fr', order: 1 })); - return [200, termQueryFindResponseDataMock]; - }); - - await termQuery.param('locale', 'fr-fr').addParams({ order: 1 }).find(); - }); - - it('should set locale query param when locale() is called', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryLocalizedFindMock); - const response = await termQuery.locale('hi-in').find(); - expect(termQuery._queryParams.locale).toBe('hi-in'); - expect(response).toEqual(termQueryLocalizedFindMock); - }); - - it('should set include_fallback query param when includeFallback() is called', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryFindResponseDataMock); - const response = await termQuery.includeFallback().find(); - expect(termQuery._queryParams.include_fallback).toBe('true'); - expect(response).toEqual(termQueryFindResponseDataMock); - }); - - it('should set both locale and include_fallback when chained', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryLocalizedFindMock); - const response = await termQuery.locale('hi-in').includeFallback().find(); - expect(termQuery._queryParams.locale).toBe('hi-in'); - expect(termQuery._queryParams.include_fallback).toBe('true'); - expect(response).toEqual(termQueryLocalizedFindMock); - }); -}); diff --git a/test/unit/term.spec.ts b/test/unit/term.spec.ts deleted file mode 100644 index 2d27d0f6..00000000 --- a/test/unit/term.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { AxiosInstance, httpClient } from '@contentstack/core'; -import MockAdapter from 'axios-mock-adapter'; -import { termQueryFindResponseDataMock, termLocalesResponseDataMock, termAncestorsResponseDataMock, termDescendantsResponseDataMock, termLocalizedFetchMock } from '../utils/mocks'; -import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; -import { Term } from '../../src/taxonomy/term'; -import { Taxonomy } from '../../src/taxonomy'; - -describe('Term class', () => { - let term: Term; - let client: AxiosInstance; - let mockClient: MockAdapter; - - beforeAll(() => { - client = httpClient(MOCK_CLIENT_OPTIONS); - mockClient = new MockAdapter(client as any); - }); - - beforeEach(() => { - term = new Term(client, 'taxonomy_testing', 'term1'); - }); - - it('should fetch the term by uid response when fetch method is called', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply(200, termQueryFindResponseDataMock.terms[0]); - - const response = await term.fetch(); - expect(response).toEqual(termQueryFindResponseDataMock.terms[0]); - }); - - it('should fetch locales for a term when locales() is called', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/locales').reply(200, termLocalesResponseDataMock.terms); - - const response = await term.locales(); - expect(response).toEqual(termLocalesResponseDataMock.terms); - }); - - it('should fetch ancestors for a term when ancestors() is called', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/ancestors').reply(200, termAncestorsResponseDataMock); - - const response = await term.ancestors(); - expect(response).toEqual(termAncestorsResponseDataMock); - }); - - it('should fetch descendants for a term when descendants() is called', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/descendants').reply(200, termDescendantsResponseDataMock); - - const response = await term.descendants(); - expect(response).toEqual(termDescendantsResponseDataMock); - }); - - it('should send depth param on descendants() when depth() is chained', async () => { - mockClient - .onGet('/taxonomies/taxonomy_testing/terms/term1/descendants') - .reply((config) => { - expect(config.params).toEqual(expect.objectContaining({ depth: 2 })); - return [200, termDescendantsResponseDataMock]; - }); - - await term.depth(2).descendants(); - }); - - it('should send include_fallback and include_branch params on fetch()', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply((config) => { - expect(config.params).toEqual(expect.objectContaining({ include_fallback: 'true', include_branch: 'true' })); - return [200, termQueryFindResponseDataMock.terms[0]]; - }); - - await term.includeFallback().includeBranch().fetch(); - }); - - it('should send arbitrary params added via param() and addParams() on ancestors()', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/ancestors').reply((config) => { - expect(config.params).toEqual(expect.objectContaining({ depth: 1, locale: 'fr-fr' })); - return [200, termAncestorsResponseDataMock]; - }); - - await term.param('depth', 1).addParams({ locale: 'fr-fr' }).ancestors(); - }); - - it('should fetch localized term when param is used to set locale', async () => { - mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply(200, termLocalizedFetchMock); - - const response = await term.param('locale', 'hi-in').fetch(); - expect(response).toEqual(termLocalizedFetchMock.term); - }); -}); diff --git a/test/unit/utils.spec.ts b/test/unit/utils.spec.ts index dab66e77..1786db3a 100644 --- a/test/unit/utils.spec.ts +++ b/test/unit/utils.spec.ts @@ -1,4 +1,4 @@ -import { buildVariantRequestHeaders, getHostforRegion, encodeQueryParams } from "../../src/common/utils"; +import { getHostforRegion, encodeQueryParams } from "../../src/common/utils"; import { Region } from "../../src/common/types"; import { DUMMY_URL, @@ -215,26 +215,6 @@ describe("Utils functions", () => { }); }); - describe("buildVariantRequestHeaders function", () => { - it("should return variant and branch headers when both are provided", () => { - expect(buildVariantRequestHeaders("variant1,variant2", "branch_name")).toEqual({ - "x-cs-variant-uid": "variant1,variant2", - branch: "branch_name", - }); - }); - - it("should return only variant header when branch is not provided", () => { - expect(buildVariantRequestHeaders("variant1")).toEqual({ - "x-cs-variant-uid": "variant1", - }); - }); - - it("should return undefined when neither variant nor branch is provided", () => { - expect(buildVariantRequestHeaders("", "")).toBeUndefined(); - expect(buildVariantRequestHeaders("", undefined)).toBeUndefined(); - }); - }); - describe("encodeQueryParams function", () => { it("should encode special characters in strings", () => { const testParams = { diff --git a/test/utils/assertion-tracker.ts b/test/utils/assertion-tracker.ts deleted file mode 100644 index a71e413d..00000000 --- a/test/utils/assertion-tracker.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Assertion tracker for the rich test report. - * - * Records every assertion (matcher, expected, received/actual, pass, negated) WITHOUT - * changing any test — specs keep using plain `expect(...)`. The rich-html-reporter - * renders these as the "Assertions Verified (Expected vs Actual)" section, mirroring - * the CMA SDK report. - * - * Mechanism: it overrides the built-in matchers via `expect.extend`, wrapping each to - * record its result and then delegating to the original built-in matcher (from the - * `expect` package). `expect.extend` writes to the shared matcher registry used by BOTH - * the global `expect` and the `@jest/globals` `expect` (which most specs import), so a - * single install covers every spec. Installed only when ENABLE_HTTP_CAPTURE=true. - */ - -export interface AssertionRec { - matcher: string; - expected: string; - actual: string; - passed: boolean; - isNot: boolean; -} - -let current: AssertionRec[] = []; - -export function clearAssertions(): void { - current = []; -} - -export function getAssertions(): AssertionRec[] { - return current.slice(); -} - -function short(v: any, limit = 800): string { - try { - if (typeof v === 'function') return `[Function${v.name ? ': ' + v.name : ''}]`; - if (typeof v === 'string') return v.length > limit ? v.slice(0, limit) + ' …' : v; - const s = JSON.stringify(v, null, 2); - if (s === undefined) return String(v); - return s.length > limit ? s.slice(0, limit) + ' …' : s; - } catch { - return String(v); - } -} - -export function installAssertionTracker(): void { - const g: any = globalThis as any; - if (g.__assertionTrackerInstalled) return; - - let builtins: Record; - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const mod = require('expect/build/matchers'); - builtins = (mod && (mod.default || mod)) as Record; - } catch { - return; // can't locate built-in matchers — skip tracking - } - if (!builtins || typeof builtins !== 'object') return; - - const wrappers: Record = {}; - for (const name of Object.keys(builtins)) { - const orig = builtins[name]; - if (typeof orig !== 'function') continue; - wrappers[name] = function (this: any, received: any, ...args: any[]) { - const result = orig.apply(this, [received, ...args]); - try { - const rawPass = !!(result && result.pass); - const isNot = !!(this && this.isNot); - current.push({ - matcher: name, - expected: args.length ? short(args.length === 1 ? args[0] : args) : '', - actual: short(received), - passed: isNot ? !rawPass : rawPass, - isNot, - }); - } catch { - /* never let recording break a test */ - } - return result; - }; - } - - // Extend every reachable expect so both global- and @jest/globals-imported specs are covered. - const extendTargets: any[] = []; - if (typeof g.expect?.extend === 'function') extendTargets.push(g.expect); - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const jg = require('@jest/globals'); - if (jg && typeof jg.expect?.extend === 'function' && jg.expect !== g.expect) { - extendTargets.push(jg.expect); - } - } catch { - /* not in jest env */ - } - for (const target of extendTargets) { - try { - target.extend(wrappers); - } catch { - /* ignore */ - } - } - - g.__assertionTrackerInstalled = true; -} diff --git a/test/utils/mocks.ts b/test/utils/mocks.ts index 38c94d3a..a265d0cd 100644 --- a/test/utils/mocks.ts +++ b/test/utils/mocks.ts @@ -1676,250 +1676,6 @@ const gfieldQueryFindResponseDataMock = { ] } -const taxonomyFindResponseDataMock = { - "taxonomies": [ - { - "uid": "taxonomy_testing", - "name": "taxonomy testing", - "description": "", - "terms_count": 1, - "created_at": "2025-10-10T06:42:48.644Z", - "updated_at": "2025-10-10T06:42:48.644Z", - "created_by": "created_by", - "updated_by": "updated_by", - "type": "TAXONOMY", - "ACL": {}, - "publish_details": { - "time": "2025-10-10T08:01:48.174Z", - "user": "user", - "environment": "env", - "locale": "en-us" - } - } - ] -} - -const termLocalesResponseDataMock = { - terms: [] -} - -const termAncestorsResponseDataMock = { - "terms": [ - { - "uid": "vehicles", - "name": "vehicles", - "publish_details": { - "time": "2025-10-28T06:54:12.505Z", - "user": "user", - "environment": "environment", - "locale": "en-us" - } - }, - { - "uid": "buses", - "name": "buses", - "publish_details": { - "time": "2025-10-28T06:54:12.514Z", - "user": "user", - "environment": "environment", - "locale": "en-us" - } - }, - { - "uid": "vrl", - "name": "vrl", - "publish_details": { - "time": "2025-10-28T06:54:12.570Z", - "user": "user", - "environment": "environment", - "locale": "en-us" - } - } - ] -} - -const termDescendantsResponseDataMock = { - "terms": [ - { - "taxonomy_uid": "taxonomy_testing", - "uid": "sleeper", - "ancestors": [ - { - "uid": "taxonomy_testing", - "name": "taxonomy_testing", - "type": "TAXONOMY" - }, - { - "uid": "vehicles", - "name": "vehicles", - "type": "" - }, - { - "uid": "buses", - "name": "buses", - "type": "" - }, - { - "uid": "vrl", - "name": "vrl", - "type": "" - } - ], - "name": "sleeper", - "parent_uid": "vrl", - "created_by": "created_by", - "created_at": "2025-10-28T07:58:46.870Z", - "updated_by": "updated_by", - "updated_at": "2025-10-28T07:58:46.870Z", - "children_count": 0, - "depth": 4, - "ACL": {}, - "publish_details": { - "time": "2025-10-28T07:59:12.557Z", - "user": "user", - "environment": "environment", - "locale": "en-us" - } - }, - { - "taxonomy_uid": "taxonomy_testing", - "uid": "intercity", - "ancestors": [ - { - "uid": "taxonomy_testing", - "name": "taxonomy_testing", - "type": "TAXONOMY" - }, - { - "uid": "vehicles", - "name": "vehicles", - "type": "" - }, - { - "uid": "buses", - "name": "buses", - "type": "" - }, - { - "uid": "vrl", - "name": "vrl", - "type": "" - } - ], - "name": "intercity", - "parent_uid": "vrl", - "created_by": "created_by", - "created_at": "2025-10-28T07:58:46.870Z", - "updated_by": "updated_by", - "updated_at": "2025-10-28T07:58:46.870Z", - "children_count": 0, - "depth": 4, - "ACL": {}, - "publish_details": { - "time": "2025-10-28T07:59:12.565Z", - "user": "user", - "environment": "environment", - "locale": "en-us" - } - } - ] -} - -const termQueryFindResponseDataMock = { - "terms": [ - { - "taxonomy_uid": "taxonomy_testing", - "uid": "term1", - "ancestors": [ - { - "uid": "taxonomy_testing", - "name": "taxonomy testing", - "type": "TAXONOMY" - } - ], - "name": "term1", - "created_by": "created_by", - "created_at": "2025-10-10T06:43:13.799Z", - "updated_by": "updated_by", - "updated_at": "2025-10-10T06:43:13.799Z", - "children_count": 0, - "depth": 1, - "ACL": {}, - "publish_details": { - "time": "2025-10-10T08:01:48.351Z", - "user": "user", - "environment": "environment", - "locale": "en-us" - } - } -] -} - -const taxonomyLocalizedFetchMock = { - "taxonomy": { - "uid": "taxonomy_testing", - "locale": "hi-in", - "name": "टैक्सोनॉमी परीक्षण", - "description": "", - "created_at": "2025-10-10T06:42:48.644Z", - "updated_at": "2025-10-10T06:42:48.644Z", - "created_by": "created_by", - "updated_by": "updated_by", - "publish_details": { - "time": "2025-10-10T08:01:48.174Z", - "user": "user", - "environment": "env", - "locale": "hi-in" - } - } -}; - -const termLocalizedFetchMock = { - "term": { - "taxonomy_uid": "taxonomy_testing", - "uid": "term1", - "locale": "hi-in", - "name": "टर्म एक", - "ancestors": [{ "uid": "taxonomy_testing", "name": "taxonomy testing", "type": "TAXONOMY" }], - "depth": 1, - "parent_uid": null, - "created_by": "created_by", - "created_at": "2025-10-10T06:43:13.799Z", - "updated_by": "updated_by", - "updated_at": "2025-10-10T06:43:13.799Z", - "publish_details": { - "time": "2025-10-10T08:01:48.351Z", - "user": "user", - "environment": "environment", - "locale": "hi-in" - } - } -}; - -const termQueryLocalizedFindMock = { - "terms": [ - { - "taxonomy_uid": "taxonomy_testing", - "uid": "term1", - "locale": "hi-in", - "name": "टर्म एक", - "ancestors": [{ "uid": "taxonomy_testing", "name": "taxonomy testing", "type": "TAXONOMY" }], - "depth": 1, - "parent_uid": null, - "created_by": "created_by", - "created_at": "2025-10-10T06:43:13.799Z", - "updated_by": "updated_by", - "updated_at": "2025-10-10T06:43:13.799Z", - "publish_details": { - "time": "2025-10-10T08:01:48.351Z", - "user": "user", - "environment": "environment", - "locale": "hi-in" - } - } - ] -}; - const syncResult: any = { ...axiosGetMock.data }; export { @@ -1932,13 +1688,5 @@ export { entryFindMock, entryFetchMock, gfieldFetchDataMock, - gfieldQueryFindResponseDataMock, - taxonomyFindResponseDataMock, - termQueryFindResponseDataMock, - termLocalesResponseDataMock, - termAncestorsResponseDataMock, - termDescendantsResponseDataMock, - taxonomyLocalizedFetchMock, - termLocalizedFetchMock, - termQueryLocalizedFindMock, + gfieldQueryFindResponseDataMock }; diff --git a/test/utils/request-capture-plugin.ts b/test/utils/request-capture-plugin.ts deleted file mode 100644 index e60f9afb..00000000 --- a/test/utils/request-capture-plugin.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Request-capture plugin for rich test reports. - * - * Implements the SDK's ContentstackPlugin interface (onRequest/onResponse), which the - * SDK registers as axios request/response interceptors. For every HTTP call made by a - * test it records: method, full URL, a copy-paste cURL command (with tokens masked), - * the inferred SDK method, request/response headers, status, duration and (truncated) - * response body. jest.setup.ts reads the last capture in afterEach and attaches it to - * the current test via jest-html-reporters' addMsg(). - * - * Enabled only when ENABLE_HTTP_CAPTURE=true, so normal test runs are unaffected. - */ - -export interface CapturedRequest { - timestamp: string; - method: string; - url: string; - requestHeaders: Record; - requestData?: any; - sdkMethod: string; - curl: string; - status?: number | null; - statusText?: string | null; - responseHeaders?: Record; - responseBody?: any; - success?: boolean; - duration?: number | null; -} - -const capturedRequests: CapturedRequest[] = []; -const MAX = 200; -// Credential-bearing header/param names (lower-cased) whose values must be masked. -// Note: sync_token / pagination_token are opaque cursors, NOT credentials, so they -// are intentionally left readable for replay/debugging. -const SENSITIVE_KEYS = [ - 'authorization', - 'authtoken', - 'access_token', - 'preview_token', - 'preview-token', - 'x-cs-preview-token', - 'live_preview', - 'management_token', - 'x-user-agent', -]; - -function isSensitive(name: string): boolean { - return SENSITIVE_KEYS.includes(String(name).toLowerCase()); -} - -function maskValue(name: string, value: any): any { - if (isSensitive(name)) { - const s = String(value); - return s.length <= 8 ? '****' : `${s.slice(0, 4)}...${s.slice(-4)}`; - } - return value; -} - -function buildFullUrl(config: any): string { - const base = (config.baseURL || '').replace(/\/$/, ''); - const path = config.url || ''; - let url = path.startsWith('http') ? path : `${base}${path.startsWith('/') ? '' : '/'}${path}`; - const params = config.params; - if (params && typeof params === 'object') { - const qs = Object.keys(params) - .filter((k) => params[k] !== undefined && params[k] !== null) - .map((k) => { - const raw = typeof params[k] === 'object' ? JSON.stringify(params[k]) : params[k]; - const v = maskValue(k, raw); - return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; - }) - .join('&'); - if (qs) url += (url.includes('?') ? '&' : '?') + qs; - } - return url; -} - -function generateCurl(config: any): string { - const method = (config.method || 'GET').toUpperCase(); - const url = buildFullUrl(config); - const parts = [`curl -X ${method} '${url}'`]; - const headers = config.headers || {}; - for (const key of Object.keys(headers)) { - // axios stores per-method header buckets (common/get/post) plus flat headers; skip the buckets - if (['common', 'get', 'post', 'put', 'patch', 'delete', 'head'].includes(key)) continue; - const val = maskValue(key, headers[key]); - parts.push(` -H '${key}: ${val}'`); - } - if (config.data) { - const body = typeof config.data === 'string' ? config.data : JSON.stringify(config.data); - parts.push(` -d '${body}'`); - } - return parts.join(' \\\n'); -} - -/** Infer a delivery-SDK call chain from the request path. */ -export function detectSdkMethod(method: string, url: string): string { - try { - const path = url.split('?')[0].replace(/^https?:\/\/[^/]+/, ''); - const m: Record = {}; - let r: RegExpMatchArray | null; - - if ((r = path.match(/\/content_types\/([^/]+)\/entries\/([^/]+)\/variants\/([^/]+)/))) { - return `stack.contentType('${r[1]}').entry('${r[2]}').variants('${r[3]}').fetch()`; - } - if ((r = path.match(/\/content_types\/([^/]+)\/entries\/([^/]+)/))) { - return `stack.contentType('${r[1]}').entry('${r[2]}').fetch()`; - } - if ((r = path.match(/\/content_types\/([^/]+)\/entries/))) { - return `stack.contentType('${r[1]}').entry().query().find()`; - } - if ((r = path.match(/\/content_types\/([^/]+)$/))) { - return `stack.contentType('${r[1]}').fetch()`; - } - if (path.match(/\/content_types$/)) return `stack.contentType().find()`; - if ((r = path.match(/\/assets\/([^/]+)/))) return `stack.asset('${r[1]}').fetch()`; - if (path.match(/\/assets$/)) return `stack.asset().query().find()`; - if ((r = path.match(/\/taxonomies\/([^/]+)\/terms/))) { - return `stack.taxonomy('${r[1]}').term().find()`; - } - if (path.match(/\/taxonomies$/)) return `stack.taxonomy().find()`; - if ((r = path.match(/\/global_fields\/([^/]+)/))) return `stack.globalField('${r[1]}').fetch()`; - if (path.match(/\/global_fields$/)) return `stack.globalField().find()`; - if (path.match(/\/stacks\/sync/)) return `stack.sync()`; - if (path.match(/\/stacks$/)) return `stack.fetch()`; - void m; - return `${method.toUpperCase()} ${path}`; - } catch { - return `${method} ${url}`; - } -} - -function normalizeHeaders(raw: any): Record { - const out: Record = {}; - if (!raw) return out; - if (typeof raw.entries === 'function') { - for (const [k, v] of raw.entries()) out[k] = maskValue(k, v); - return out; - } - for (const k of Object.keys(raw)) { - if (['common', 'get', 'post', 'put', 'patch', 'delete', 'head'].includes(k)) continue; - out[k] = maskValue(k, raw[k]); - } - return out; -} - -function truncate(data: any, limit = 4000): any { - try { - const s = typeof data === 'string' ? data : JSON.stringify(data); - if (s && s.length > limit) return s.slice(0, limit) + `\n… [truncated ${s.length - limit} chars]`; - return data; - } catch { - return data; - } -} - -export const requestCapturePlugin = { - onRequest(request: any) { - request._startTime = Date.now(); - capturedRequests.push({ - timestamp: new Date().toISOString(), - method: (request.method || 'GET').toUpperCase(), - url: buildFullUrl(request), - requestHeaders: normalizeHeaders(request.headers), - requestData: request.data, - sdkMethod: detectSdkMethod(request.method || 'GET', buildFullUrl(request)), - curl: generateCurl(request), - status: null, - }); - if (capturedRequests.length > MAX) capturedRequests.shift(); - return request; - }, - - onResponse(request: any, response: any, _data: any) { - const res = response || {}; - const cfg = res.config || request || {}; - const url = buildFullUrl(cfg); - // Update the matching captured request (last one for this url) or push a fresh entry. - let entry = [...capturedRequests].reverse().find((c) => c.url === url && c.status == null); - if (!entry) { - entry = { - timestamp: new Date().toISOString(), - method: (cfg.method || 'GET').toUpperCase(), - url, - requestHeaders: normalizeHeaders(cfg.headers), - requestData: cfg.data, - sdkMethod: detectSdkMethod(cfg.method || 'GET', url), - curl: generateCurl(cfg), - }; - capturedRequests.push(entry); - } - entry.status = res.status ?? null; - entry.statusText = res.statusText ?? null; - entry.responseHeaders = normalizeHeaders(res.headers); - entry.responseBody = truncate(res.data); - entry.success = res.status ? res.status >= 200 && res.status < 400 : undefined; - entry.duration = cfg._startTime ? Date.now() - cfg._startTime : null; - return response; - }, -}; - -export function getLastCapturedRequest(): CapturedRequest | undefined { - return capturedRequests[capturedRequests.length - 1]; -} - -export function getCapturedRequests(): CapturedRequest[] { - return capturedRequests.slice(); -} - -export function clearCapturedRequests(): void { - capturedRequests.length = 0; -} diff --git a/test/utils/stack-instance.ts b/test/utils/stack-instance.ts index 57f37b3c..fdab8df5 100644 --- a/test/utils/stack-instance.ts +++ b/test/utils/stack-instance.ts @@ -1,7 +1,6 @@ import dotenv from 'dotenv'; import * as contentstack from '../../src/stack'; import { StackConfig } from '../../src/common/types'; -import { requestCapturePlugin } from './request-capture-plugin'; dotenv.config(); @@ -18,11 +17,6 @@ function stackInstance() { } }; - // Attach the HTTP request-capture plugin for rich test reports (opt-in). - if (process.env.ENABLE_HTTP_CAPTURE === 'true') { - params.plugins = [requestCapturePlugin]; - } - return contentstack.stack(params); }