diff --git a/.changeset/README.md b/.changeset/README.md
new file mode 100644
index 0000000000..df6bad5cba
--- /dev/null
+++ b/.changeset/README.md
@@ -0,0 +1,26 @@
+# Changesets
+
+This directory is used by [@changesets/cli](https://github.com/changesets/changesets) to manage versioning and changelog entries for jira.js.
+
+## Workflow
+
+1. **Make changes** — implement your feature or fix
+2. **Add a changeset** — `pnpm changeset` (interactive prompt)
+3. **Commit** — include the generated `.md` file in your commit
+4. **Release** — maintainers run `pnpm changeset version` to bump versions, then publish
+
+## Changeset types
+
+| Type | When |
+|------|------|
+| `major` | Breaking changes (see `SEMVER_POLICY.md`) |
+| `minor` | Additive changes (new endpoints, new exports) |
+| `patch` | Bug fixes, internal improvements, declaration fixes |
+
+## Rules
+
+- Every PR that affects public API **must** include a changeset
+- PRs that only affect tests, docs, or CI may omit a changeset
+- Breaking changes require a `major` changeset with migration guidance
+- See `SEMVER_POLICY.md` for exact classification rules
+- See `DEPRECATION_POLICY.md` for the deprecation lifecycle
diff --git a/.changeset/config.json b/.changeset/config.json
new file mode 100644
index 0000000000..8e6693cd9c
--- /dev/null
+++ b/.changeset/config.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config/schema.json",
+ "changelog": "@changesets/cli/changelog",
+ "commit": false,
+ "fixed": [],
+ "linked": [],
+ "access": "public",
+ "baseBranch": "master",
+ "updateInternalDependencies": "patch",
+ "ignore": []
+}
diff --git a/.changeset/shaky-dragons-scream.md b/.changeset/shaky-dragons-scream.md
new file mode 100644
index 0000000000..35c0ca3971
--- /dev/null
+++ b/.changeset/shaky-dragons-scream.md
@@ -0,0 +1,8 @@
+---
+'@jira.js/servicedesk': patch
+'@jira.js/agile': patch
+'@jira.js/cloud': patch
+'@jira.js/base': patch
+---
+
+Initial release
diff --git a/.env.example b/.env.example
index 8b72158164..b736da6eb5 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,15 @@
-HOST=
-EMAIL=
-API_TOKEN=
+# Basic Auth — required for all live tests
+JIRA_BASE_URL=https://your-domain.atlassian.net
+JIRA_EMAIL=you@example.com
+JIRA_API_TOKEN=your-api-token
+
+# Optional — reuse an existing project instead of creating one per run
+JIRA_TEST_PROJECT_KEY=
+
+# OAuth 2.0 — required for Feature Flags, Builds, Deployments, Dev Info live tests
+# Run `pnpm tsx scripts/oauth-setup.ts` to obtain tokens
+JIRA_OAUTH_CLIENT_ID=
+JIRA_OAUTH_CLIENT_SECRET=
+JIRA_CLOUD_ID=
+JIRA_OAUTH_TOKEN=
+JIRA_OAUTH_REFRESH_TOKEN=
diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml
new file mode 100644
index 0000000000..696d69f8db
--- /dev/null
+++ b/.github/workflows/changeset-check.yml
@@ -0,0 +1,65 @@
+name: 📦 Changeset Check
+
+on:
+ pull_request:
+ branches:
+ - master
+ - develop
+
+jobs:
+ changeset-check:
+ name: 📦 Changeset Required
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: 🔄 Checkout sources
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: ⚙️ Use Node.js 22.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+
+ - name: 📦 Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: 📌 Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: 📦 Check for changeset
+ run: |
+ # Get list of changed files in this PR vs base branch
+ CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
+ echo "Changed files:"
+ echo "$CHANGED"
+
+ # Check if any package source files changed
+ SRC_CHANGED=$(echo "$CHANGED" | grep -E '^packages/[^/]+/src/' || true)
+
+ if [ -z "$SRC_CHANGED" ]; then
+ echo "✅ No package source files changed — changeset not required"
+ exit 0
+ fi
+
+ echo "Source files changed:"
+ echo "$SRC_CHANGED"
+
+ # Check if any .md changeset files exist (excluding README.md)
+ CHANGESETS=$(ls .changeset/*.md 2>/dev/null | grep -v README.md || true)
+
+ if [ -z "$CHANGESETS" ]; then
+ echo ""
+ echo "❌ Package source files changed but no changeset found."
+ echo ""
+ echo " Run: pnpm changeset"
+ echo " This creates a changeset file describing the impact of your changes."
+ echo " See .changeset/README.md and SEMVER_POLICY.md for guidance."
+ echo ""
+ exit 1
+ fi
+
+ echo "✅ Changeset found:"
+ echo "$CHANGESETS"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a88da40a90..d8306be589 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -3,9 +3,9 @@ name: 🛠️ CI
on:
push:
branches:
- - '**' # Runs on all branch pushes
+ - '**'
tags-ignore:
- - '**' # Ignore all tag pushes
+ - '**'
repository_dispatch:
types: [ pr-approved ]
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [20.x, 22.x]
+ node-version: [22.x, 24.x]
steps:
- name: 🔄 Checkout sources
uses: actions/checkout@v4
@@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [20.x, 22.x]
+ node-version: [22.x, 24.x]
steps:
- name: 🔄 Checkout sources
uses: actions/checkout@v4
@@ -53,13 +53,13 @@ jobs:
env:
CI: true
- test_unit:
+ test:
name: 🧪 Unit Tests
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [20.x, 22.x]
+ node-version: [22.x, 24.x]
steps:
- name: 🔄 Checkout sources
uses: actions/checkout@v4
@@ -69,37 +69,65 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: 📦 Install pnpm
uses: pnpm/action-setup@v4
- - name: 📌 Installing dependencies
+ - name: 📌 Install dependencies
run: pnpm install --frozen-lockfile
- - name: 🚀 Running unit tests
- run: pnpm run test:unit
+ - name: 🏗️ Build packages
+ run: pnpm run build
+ - name: 🧪 Unit tests — @jira.js/base
+ run: pnpm --filter @jira.js/base test:unit
+ - name: 🧪 Unit tests — @jira.js/cloud
+ run: pnpm --filter @jira.js/cloud test:unit
- test_integration:
- name: 🧩 Integration Tests
- needs:
- - lint
- - test_unit
+ governance:
+ name: 🔏 API Governance
+ needs: build
runs-on: ubuntu-latest
- strategy:
- max-parallel: 1
- matrix:
- node-version: [20.x, 22.x]
+ permissions:
+ contents: read
steps:
- name: 🔄 Checkout sources
uses: actions/checkout@v4
- - name: ⚙️ Use Node.js ${{ matrix.node-version }}
+ - name: ⚙️ Use Node.js 22.x
uses: actions/setup-node@v4
with:
- node-version: ${{ matrix.node-version }}
+ node-version: 22.x
- name: 📦 Install pnpm
uses: pnpm/action-setup@v4
- - name: 📌 Installing dependencies
+ - name: 📌 Install dependencies
run: pnpm install --frozen-lockfile
- - name: 📝 Creating `.env` file
- run: |
- touch .env
- echo HOST=${{ secrets.HOST }} >> .env
- echo EMAIL=${{ secrets.EMAIL }} >> .env
- echo API_TOKEN=${{ secrets.API_TOKEN }} >> .env
- - name: 🚀 Running integration tests
- run: pnpm run test:integration
+ - name: 🏗️ Build packages
+ run: pnpm run build
+ - name: 📋 Validate package exports map
+ run: node scripts/check-exports.mjs
+ - name: 🔍 Check API surface snapshots
+ run: node scripts/api-surface.mjs check
+
+ release-cert:
+ name: 🎓 Release Certification
+ needs: [build, governance]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: 🔄 Checkout sources
+ uses: actions/checkout@v4
+ - name: ⚙️ Use Node.js 22.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+ - name: 📦 Install pnpm
+ uses: pnpm/action-setup@v4
+ - name: 📌 Install dependencies
+ run: pnpm install --frozen-lockfile
+ - name: 🏗️ Build packages
+ run: pnpm run build
+ - name: 🗜️ Validate packed tarballs
+ run: node scripts/pack-validate.mjs
+ - name: 🔬 Type resolution check (ATTW)
+ run: node scripts/attw-check.mjs
+ - name: 📏 Install size governance
+ run: node scripts/size-check.mjs
+ - name: 🧪 Consumer smoke tests
+ run: node scripts/smoke-consumers.mjs
+ env:
+ npm_config_prefer_offline: 'false'
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000000..e4389a0379
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,95 @@
+name: 📚 Docs
+
+on:
+ push:
+ branches: [master, develop]
+ paths:
+ - 'docs/**'
+ - 'packages/*/src/**'
+ - 'typedoc.json'
+ - '.github/workflows/docs.yml'
+ pull_request:
+ branches: [master, develop]
+ paths:
+ - 'docs/**'
+ - 'packages/*/src/**'
+ - 'typedoc.json'
+ workflow_dispatch:
+
+jobs:
+ docs-build:
+ name: 🏗️ Build docs
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: 🔄 Checkout
+ uses: actions/checkout@v4
+
+ - name: ⚙️ Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+
+ - name: 📦 Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: 📌 Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: 🏗️ Build packages (for TypeDoc)
+ run: pnpm run build
+
+ - name: 📖 Generate API reference
+ run: pnpm docs:api
+
+ - name: 🏗️ Build VitePress site
+ run: pnpm docs:build
+
+ docs-deploy:
+ name: 🚀 Deploy to GitHub Pages
+ needs: docs-build
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master' && github.event_name == 'push'
+ permissions:
+ contents: read
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: 🔄 Checkout
+ uses: actions/checkout@v4
+
+ - name: ⚙️ Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+
+ - name: 📦 Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: 📌 Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: 🏗️ Build packages
+ run: pnpm run build
+
+ - name: 📖 Generate API reference
+ run: pnpm docs:api
+
+ - name: 🏗️ Build docs site
+ run: pnpm docs:build
+
+ - name: ⚙️ Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: 📤 Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: docs/.vitepress/dist
+
+ - name: 🚀 Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml
index 4ddb55b2d9..a70b054d69 100644
--- a/.github/workflows/publish-dev.yml
+++ b/.github/workflows/publish-dev.yml
@@ -4,7 +4,7 @@ on:
workflow_dispatch
env:
- NODE_VERSION: '20.x'
+ NODE_VERSION: '22.x'
permissions:
contents: read
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 9ffa1c6ae9..c2970425a5 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -6,14 +6,14 @@ on:
- 'v*.*.*'
env:
- NODE_VERSION: '20.x'
+ NODE_VERSION: '22.x'
permissions:
contents: read
jobs:
build-and-test:
- name: 🏗️ Build and Test
+ name: 🏗️ Build and Lint
runs-on: ubuntu-latest
steps:
- name: 🔄 Checkout repository
@@ -40,16 +40,6 @@ jobs:
env:
CI: true
- - name: 🧪 Run unit tests
- run: pnpm run test:unit
-
- - name: 🧩 Run integration tests
- run: pnpm run test:integration
- env:
- HOST: ${{ secrets.HOST }}
- EMAIL: ${{ secrets.EMAIL }}
- API_TOKEN: ${{ secrets.API_TOKEN }}
-
publish-package:
name: 🚀 Publish Package
needs: build-and-test
@@ -111,36 +101,3 @@ jobs:
run: pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
-
- deploy-documentation:
- name: 📚 Deploy Documentation
- needs: publish-package
- runs-on: ubuntu-latest
- steps:
- - name: 🔄 Checkout repository
- uses: actions/checkout@v4
- with:
- ref: master
-
- - name: ⚙️ Setup Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
-
- - name: 📦 Install pnpm
- uses: pnpm/action-setup@v4
-
- - name: 📌 Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: 📝 Generate documentation
- run: pnpm run doc
-
- - name: 🚀 Deploy to docs branch
- uses: JamesIves/github-pages-deploy-action@v4
- with:
- branch: docs
- folder: docs
- clean: true
- token: '${{ secrets.PAT }}'
- commit-message: "docs: Update documentation for v${{ needs.publish-package.outputs.version }} [skip ci]"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..a1062cdcac
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,70 @@
+name: 🚀 Release
+
+on:
+ push:
+ branches:
+ - master
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+jobs:
+ release:
+ name: 🚀 Release
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write # create git tags, push version commits
+ pull-requests: write # create "Version Packages" PR
+ id-token: write # npm provenance attestation
+
+ steps:
+ - name: 🔄 Checkout sources
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: ⚙️ Use Node.js 22.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+ registry-url: https://registry.npmjs.org
+
+ - name: 📦 Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: 📌 Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: 🏗️ Build all packages
+ run: pnpm run build
+
+ - name: 🔏 API Governance
+ run: pnpm run api:governance
+
+ - name: 🗜️ Validate packed tarballs
+ run: node scripts/pack-validate.mjs
+
+ - name: 🔬 Type resolution check (ATTW)
+ run: node scripts/attw-check.mjs
+
+ - name: 📏 Install size governance
+ run: node scripts/size-check.mjs
+
+ - name: 🧪 Consumer smoke tests
+ run: node scripts/smoke-consumers.mjs
+ env:
+ npm_config_prefer_offline: 'false'
+
+ # Changesets action:
+ # - If changesets are pending → creates/updates "Version Packages" PR
+ # - If this IS the version commit (no pending changesets) → publishes to npm
+ - name: 📦 Create Release PR or Publish
+ id: changesets
+ uses: changesets/action@v1
+ with:
+ publish: pnpm run release:publish
+ title: 'chore(release): version packages'
+ commit: 'chore(release): version packages'
+ createGithubReleases: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 4e3c836361..7b87439ccf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,12 @@ node_modules/
dist/
docs/
coverage/
+dist-pack/
+graphify-out/
+.graphify_batches/
yarn.lock
yarn-error.log
.DS_Store
.env
+*.tsbuildinfo
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000000..13fa3e49cc
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1 @@
+node_modules/.bin/commitlint --edit "$1"
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000000..d609364c88
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+pnpm run api:governance
diff --git a/.npmignore b/.npmignore
index 10b3f984c4..1c20431a12 100644
--- a/.npmignore
+++ b/.npmignore
@@ -5,6 +5,8 @@ coverage
.github
.idea
examples
+scripts
+consumers
eslint.config.ts
.editorconfig
diff --git a/README.md b/README.md
index c7a168555d..a42c441547 100644
--- a/README.md
+++ b/README.md
@@ -1,397 +1,124 @@

-

-

+

-

-

+

+

-
JavaScript / TypeScript library for Node.js and browsers to interact with Atlassian Jira APIs
+
Type-safe, tree-shakable TypeScript SDK for the Jira Cloud REST API
-## About
+## Packages
-**Jira.js** is a powerful, production-ready [Node.js](https://nodejs.org/) and browser-compatible TypeScript library that provides seamless interaction with Atlassian Jira Cloud APIs. This npm package offers comprehensive support for:
+| Package | Description |
+|---------|-------------|
+| [`@jira.js/cloud`](./packages/cloud) | Jira Cloud REST API v3 — 89 namespaces |
+| [`@jira.js/agile`](./packages/agile) | Jira Agile API — boards, sprints, epics |
+| [`@jira.js/base`](./packages/base) | Transport, auth, error types, retry |
-- **[Jira Cloud REST API v2/v3](https://developer.atlassian.com/cloud/jira/platform/rest/)** - Complete platform API coverage
-- **[Jira Agile Cloud API](https://developer.atlassian.com/cloud/jira/software/rest/intro/)** - Sprint, board, and backlog management
-- **[Jira Service Desk Cloud API](https://developer.atlassian.com/cloud/jira/service-desk/rest/intro/)** - Service desk operations
-
-### Key Features
-
-- ✅ **Type-Safe**: Full TypeScript support with comprehensive type definitions and IntelliSense
-- ✅ **Tree-Shakable**: Optimize bundle size by importing only what you need (perfect for browser apps)
-- ✅ **Universal**: Works in Node.js (v20+) and modern browsers with full ESM/CJS support
-- ✅ **Complete Coverage**: Nearly 100% of Jira Cloud REST API v2/v3, Agile, and Service Desk APIs
-- ✅ **Well Documented**: Extensive JSDoc, API reference, and code examples
-- ✅ **Modern Stack**: Built with TypeScript, supports ES Modules and CommonJS
-- ✅ **Actively Maintained**: Regular updates with new Jira API features and bug fixes
-- ✅ **Production Ready**: Used by thousands of developers in production environments
-
-Perfect for building Jira integrations, automation tools, webhooks, CI/CD pipelines, custom Jira applications, and browser-based Jira management tools.
-
-## Table of Contents
-
-- [Getting Started](#getting-started)
- - [Installation](#installation)
- - [Quick Example](#quick-example)
-- [Documentation](#documentation)
-- [Usage](#usage)
- - [Authentication](#authentication)
- - [Email and API Token](#email-and-api-token)
- - [OAuth 2.0](#oauth-20)
- - [Error Handling](#error-handling)
- - [API Structure](#api-structure)
-- [Tree Shaking](#tree-shaking)
-- [Other Products](#other-products)
-- [License](#license)
-
-## Getting Started
-
-### Installation
-
-Install the Jira.js npm package using your preferred package manager. **Requires Node.js 20.0.0 or newer.**
+## Quick start
```bash
-# Using npm
-npm install jira.js
-
-# Using yarn
-yarn add jira.js
-
-# Using pnpm
-pnpm add jira.js
+pnpm add @jira.js/cloud
```
-**TypeScript users**: Type definitions are included - no additional `@types` package needed!
-
-### Quick Example
-
-Get started with Jira.js in under 5 minutes. This example shows how to create a Jira issue using the TypeScript client:
-
```typescript
-import { Version3Client } from 'jira.js';
+import { createCloudClient } from '@jira.js/cloud';
-const client = new Version3Client({
+const client = createCloudClient({
host: 'https://your-domain.atlassian.net',
- authentication: {
- basic: {
- email: 'your@email.com',
- apiToken: 'YOUR_API_TOKEN', // Create one: https://id.atlassian.com/manage-profile/security/api-tokens
- },
- },
+ auth: { type: 'basic', email: 'you@example.com', apiToken: 'YOUR_API_TOKEN' },
});
-async function createIssue() {
- const project = await client.projects.getProject({ projectIdOrKey: 'Your project id or key' });
-
- const newIssue = await client.issues.createIssue({
- fields: {
- summary: 'Hello Jira.js!',
- issuetype: { name: 'Task' },
- project: { key: project.key },
- },
- });
-
- console.log(`Issue created: ${newIssue.id}`);
-}
-
-createIssue();
+const issue = await client.issues.getIssue({ issueIdOrKey: 'PROJ-1' });
+console.log(issue.fields.summary);
```
-## Documentation
-
-📚 **Full API reference, guides, and examples** available at:
-**[https://mrrefactoring.github.io/jira.js/](https://mrrefactoring.github.io/jira.js/)**
+## Features
-The documentation includes:
-- Complete API reference for all endpoints
-- TypeScript examples and code samples
-- Authentication guides
-- Error handling patterns
-- Best practices and tips
+- **Type-safe** — every request and response fully typed; catch mistakes at compile time
+- **Tree-shakable** — import only the namespaces you use
+- **ESM + CJS** — dual build, works in Node.js 22+ and modern bundlers
+- **0 ATTW warnings** — clean declarations in all TypeScript resolution modes
+- **Provenance-signed** — cryptographic npm publish attestations via GitHub Actions
+- **OAuth 2.0** — built-in 3LO helpers for Atlassian OAuth
+- **Automatic retry** — `withRetry` handles 429/502/503/504 with exponential backoff
-## Supported APIs
+## Requirements
-Jira.js provides comprehensive support for all major Jira Cloud APIs:
+- Node.js >= 22
+- TypeScript >= 6
+- `moduleResolution: Bundler` or `NodeNext`
-- **Jira Platform REST API v2**: Legacy API endpoints for projects, issues, users, and more
-- **Jira Platform REST API v3**: Modern API with enhanced features and improved performance
-- **Jira Software (Agile) API**: Sprint management, boards, backlogs, and agile workflows
-- **Jira Service Desk API**: Service desk operations, customer management, and request handling
-
-All APIs are fully typed with TypeScript definitions, making development faster and safer.
-
-## Usage
-
-### Authentication
-
-#### Email and API Token
-
-1. Create an API token: [https://id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
-2. Configure the client:
+## Authentication
```typescript
-const client = new Version3Client({
- host: 'https://your-domain.atlassian.net',
- authentication: {
- basic: { email: 'YOUR@EMAIL.ORG', apiToken: 'YOUR_API_TOKEN' },
- },
-});
-```
+// Email + API token
+auth: { type: 'basic', email: 'you@example.com', apiToken: 'TOKEN' }
-#### OAuth 2.0
+// Bearer token
+auth: { type: 'bearer', token: 'MY_TOKEN' }
-Only authorization code grants are supported. Implement your own token acquisition flow using Atlassian's [OAuth 2.0 documentation](https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/).
-
-```typescript
-const client = new Version3Client({
- host: 'https://your-domain.atlassian.net',
- authentication: {
- oauth2: { accessToken: 'YOUR_ACCESS_TOKEN' },
- },
-});
+// OAuth 2.0 (dynamic token with auto-refresh)
+auth: { type: 'bearer', getToken: async () => myStore.getAccessToken() }
```
-### Error Handling
-
-Errors are categorized as:
-- `HttpException`: Server responded with an error (includes parsed error details)
-- `AxiosError`: Network/configuration issues (e.g., timeouts)
+Create an API token at [https://id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
-**Example handling:**
+## Error handling
```typescript
+import { ApiError, withRetry } from '@jira.js/base';
+
+// Basic error handling
try {
- await client.issues.getIssue({ issueIdOrKey: 'INVALID-123' });
-} catch (error) {
- if (error instanceof HttpException) {
- console.error('Server error:', error.message);
- console.debug('Response headers:', error.cause.response?.headers);
- } else if (error instanceof AxiosError) {
- console.error('Network error:', error.code);
- } else {
- console.error('Unexpected error:', error);
+ const issue = await client.issues.getIssue({ issueIdOrKey: 'PROJ-1' });
+} catch (err) {
+ if (err instanceof ApiError) {
+ console.log(err.status, err.message);
}
}
-```
-
-### API Structure
-
-Access endpoints using the `client..` pattern:
-
-```typescript
-// Get all projects
-const projects = await client.projects.searchProjects();
-// Create a sprint
-const sprint = await client.sprint.createSprint({ name: 'Q4 Sprint' });
+// Automatic retry on 429/502/503/504
+const issue = await withRetry(
+ () => client.issues.getIssue({ issueIdOrKey: 'PROJ-1' }),
+ { maxAttempts: 4 },
+);
```
-**Available API groups:**
-
- 🔽 Agile Cloud API
-
- - [backlog](https://developer.atlassian.com/cloud/jira/software/rest/api-group-backlog/#api-group-backlog)
- - [board](https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-group-board)
- - [builds](https://developer.atlassian.com/cloud/jira/software/rest/api-group-builds/#api-group-builds)
- - [deployments](https://developer.atlassian.com/cloud/jira/software/rest/api-group-deployments/#api-group-deployments)
- - [developmentInformation](https://developer.atlassian.com/cloud/jira/software/rest/api-group-development-information/#api-group-development-information)
- - [devopsComponents](https://developer.atlassian.com/cloud/jira/software/rest/api-group-devops-components/#api-group-devops-components)
- - [epic](https://developer.atlassian.com/cloud/jira/software/rest/api-group-epic/#api-group-epic)
- - [featureFlags](https://developer.atlassian.com/cloud/jira/software/rest/api-group-feature-flags/#api-group-feature-flags)
- - [issue](https://developer.atlassian.com/cloud/jira/software/rest/api-group-issue/#api-group-issue)
- - [operations](https://developer.atlassian.com/cloud/jira/software/rest/api-group-operations/#api-group-operations)
- - [remoteLinks](https://developer.atlassian.com/cloud/jira/software/rest/api-group-remote-links/#api-group-remote-links)
- - [securityInformation](https://developer.atlassian.com/cloud/jira/software/rest/api-group-security-information/#api-group-security-information)
- - [sprint](https://developer.atlassian.com/cloud/jira/software/rest/api-group-sprint/#api-group-sprint)
-
-
-
- 🔽 Core REST API (v2/v3)
-
- - [api](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-other-operations/#api-group-other-operations)
- - [announcementBanner](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-announcement-banner/#api-group-announcement-banner)
- - [appDataPolicy](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-app-data-policies/#api-group-app-data-policies)
- - [applicationRoles](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-application-roles/#api-group-application-roles)
- - [appMigration](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-app-migration/#api-group-app-migration)
- - [auditRecords](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-audit-records/#api-group-audit-records)
- - [avatars](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-avatars/#api-group-avatars)
- - [classificationLevels](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-classification-levels/#api-group-classification-levels)
- - [dashboards](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-dashboards/#api-group-dashboards)
- - [filters](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-filters/#api-group-filters)
- - [fieldSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-field-schemes/#api-group-field-schemes)
- - [filterSharing](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-filter-sharing/#api-group-filter-sharing)
- - [groupAndUserPicker](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-group-and-user-picker/#api-group-group-and-user-picker)
- - [groups](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-groups/#api-group-groups)
- - [instanceInformation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-instance-information/#api-group-instance-information)
- - [issues](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-group-issues)
- - [issueAttachments](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-group-issue-attachments)
- - [issueBulkOperations](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-bulk-operations/#api-group-issue-bulk-operations)
- - [issueComments](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-group-issue-comments)
- - [issueCustomFieldAssociations](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-associations/#api-group-issue-custom-field-associations)
- - [issueCustomFieldConfigurationApps](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-configuration--apps-/#api-group-issue-custom-field-configuration--apps-)
- - [issueCommentProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comment-properties/#api-group-issue-comment-properties)
- - [issueFields](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-fields/#api-group-issue-fields)
- - [issueFieldConfigurations](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-field-configurations/#api-group-issue-field-configurations)
- - [issueCustomFieldContexts](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-contexts/#api-group-issue-custom-field-contexts)
- - [issueCustomFieldOptions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-options/#api-group-issue-custom-field-options)
- - [issueCustomFieldOptionsApps](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-options--apps-/#api-group-issue-custom-field-options--apps-)
- - [issueCustomFieldValuesApps](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-values--apps-/#api-group-issue-custom-field-values--apps-)
- - [issueLinks](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-links/#api-group-issue-links)
- - [issueLinkTypes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-link-types/#api-group-issue-link-types)
- - [issueNavigatorSettings](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-navigator-settings/#api-group-issue-navigator-settings)
- - [issueNotificationSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-notification-schemes/#api-group-issue-notification-schemes)
- - [issuePriorities](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-priorities/#api-group-issue-priorities)
- - [issueProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-properties/#api-group-issue-properties)
- - [issueRedaction](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-redaction/#api-group-issue-redaction)
- - [issueRemoteLinks](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-remote-links/#api-group-issue-remote-links)
- - [issueResolutions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-resolutions/#api-group-issue-resolutions)
- - [issueSearch](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-group-issue-search)
- - [issueSecurityLevel](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-security-level/#api-group-issue-security-level)
- - [issueSecuritySchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-security-schemes/#api-group-issue-security-schemes)
- - [issueTypes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-types/#api-group-issue-types)
- - [issueTypeSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-type-schemes/#api-group-issue-type-schemes)
- - [issueTypeScreenSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-type-screen-schemes/#api-group-issue-type-screen-schemes)
- - [issueTypeProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-type-properties/#api-group-issue-type-properties)
- - [issueVotes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-votes/#api-group-issue-votes)
- - [issueWatchers](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-watchers/#api-group-issue-watchers)
- - [issueWorklogs](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-worklogs/#api-group-issue-worklogs)
- - [issueWorklogProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-worklog-properties/#api-group-issue-worklog-properties)
- - [jiraExpressions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-jira-expressions/#api-group-jira-expressions)
- - [jiraSettings](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-jira-settings/#api-group-jira-settings)
- - [jql](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-jql/#api-group-jql)
- - [jqlFunctionsApps](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-jql-functions--apps-/#api-group-jql-functions--apps-)
- - [labels](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-labels/#api-group-labels)
- - [licenseMetrics](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-license-metrics/#api-group-license-metrics)
- - [migrationOfConnectModulesToForge](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-migration-of-connect-modules-to-forge/#api-group-migration-of-connect-modules-to-forge)
- - [myself](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-myself/#api-group-myself)
- - [permissions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-permissions/#api-group-permissions)
- - [permissionSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-permission-schemes/#api-group-permission-schemes)
- - [plans](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-plans/#api-group-plans)
- - [prioritySchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-priority-schemes/#api-group-priority-schemes)
- - [projects](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-group-projects)
- - [projectTemplates](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-templates/#api-group-project-templates)
- - [projectAvatars](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-avatars/#api-group-project-avatars)
- - [projectCategories](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-categories/#api-group-project-categories)
- - [projectClassificationLevels](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-classification-levels/#api-group-project-classification-levels)
- - [projectComponents](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-group-project-components)
- - [projectEmail](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-email/#api-group-project-email)
- - [projectFeatures](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-features/#api-group-project-features)
- - [projectKeyAndNameValidation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-key-and-name-validation/#api-group-project-key-and-name-validation)
- - [projectPermissionSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-permission-schemes/#api-group-project-permission-schemes)
- - [projectProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/#api-group-project-properties)
- - [projectRoles](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-roles/#api-group-project-roles)
- - [projectRoleActors](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-role-actors/#api-group-project-role-actors)
- - [projectTypes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-types/#api-group-project-types)
- - [projectVersions](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-versions/#api-group-project-versions)
- - [screens](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-screens/#api-group-screens)
- - [screenTabs](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-screen-tabs/#api-group-screen-tabs)
- - [screenTabFields](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-screen-tab-fields/#api-group-screen-tab-fields)
- - [screenSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-screen-schemes/#api-group-screen-schemes)
- - [serverInfo](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-server-info/#api-group-server-info)
- - [serviceRegistry](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-service-registry/#api-group-service-registry)
- - [status](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-status/#api-group-status)
- - [tasks](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-tasks/#api-group-tasks)
- - [teamsInPlan](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-teams-in-plan/#api-group-teams-in-plan)
- - [timeTracking](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-time-tracking/#api-group-time-tracking)
- - [uiModificationsApps](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-ui-modifications--apps-/#api-group-ui-modifications--apps-)
- - [users](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-users/#api-group-users)
- - [userNavProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-other-operations/#api-group-other-operations)
- - [userProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-user-properties/#api-group-user-properties)
- - [userSearch](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-user-search/#api-group-user-search)
- - [webhooks](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-webhooks/#api-group-webhooks)
- - [workflows](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflows/#api-group-workflows)
- - [workflowTransitionRules](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-transition-rules/#api-group-workflow-transition-rules)
- - [workflowSchemes](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-schemes/#api-group-workflow-schemes)
- - [workflowSchemeProjectAssociations](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-scheme-project-associations/#api-group-workflow-scheme-project-associations)
- - [workflowSchemeDrafts](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-scheme-drafts/#api-group-workflow-scheme-drafts)
- - [workflowStatuses](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-statuses/#api-group-workflow-statuses)
- - [workflowStatusCategories](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-group-workflow-status-categories)
- - [workflowTransitionProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-transition-properties/#api-group-workflow-transition-properties)
- - [appProperties](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-app-properties/#api-group-app-properties)
- - [dynamicModules](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-dynamic-modules/#api-group-dynamic-modules)
-
-
-
- 🔽 Service Desk API
-
- - [customer](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/)
- - [info](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-info/#api-group-info)
- - [insight](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-insight/#api-group-insight)
- - [knowledgeBase](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-knowledgebase/#api-group-knowledgebase)
- - [organizations](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization)
- - [request](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-group-request)
- - [requestType](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-requesttype/#api-group-requesttype)
- - [serviceDesk](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-group-servicedesk)
-
-
-See full group list in [original documentation](#usage).
-
-## Tree Shaking & Bundle Optimization
-
-Jira.js supports tree shaking to minimize your bundle size. Import only the modules you need:
+## Agile API
```typescript
-// custom-client.ts
-import { BaseClient } from 'jira.js';
-import { Issues } from 'jira.js/version3';
-import { Board } from 'jira.js/agile';
-
-export class CustomClient extends BaseClient {
- issues = new Issues(this);
- board = new Board(this);
-}
-
-// Usage
-const client = new CustomClient({ /* config */ });
-await client.issues.getIssue({ issueIdOrKey: 'KEY-1' });
-```
-
-**Benefits:**
-- Smaller bundle sizes for browser applications
-- Faster load times
-- Better performance in production
-
-## Use Cases
-
-Jira.js is perfect for:
+import { createAgileClient } from '@jira.js/agile';
-- 🔄 **CI/CD Integration**: Automate issue creation and updates in your deployment pipelines
-- 🤖 **Automation Scripts**: Build custom automation for Jira workflows and processes
-- 📊 **Reporting & Analytics**: Extract and analyze Jira data for custom dashboards
-- 🔗 **Webhook Handlers**: Process Jira webhooks and integrate with external systems
-- 🛠️ **Custom Tools**: Build admin tools, migration scripts, and custom Jira applications
-- 📱 **Browser Apps**: Create browser-based Jira management interfaces
-- 🔌 **Third-Party Integrations**: Connect Jira with other services and platforms
-
-## Common Questions
-
-**Q: Does this work with Jira Server/Data Center?**
-A: No, Jira.js is designed specifically for Jira Cloud. For on-premise Jira, consider using the REST API directly.
+const agile = createAgileClient({
+ host: 'https://your-domain.atlassian.net',
+ auth: { type: 'basic', email: 'you@example.com', apiToken: 'TOKEN' },
+});
-**Q: Is TypeScript required?**
-A: No, but TypeScript is fully supported with comprehensive type definitions. You can use Jira.js with plain JavaScript too.
+const boards = await agile.board.getAllBoards({ type: 'scrum' });
+const sprints = await agile.sprint.getAllSprints({ boardId: boards.values![0].id!, state: 'active' });
+```
-**Q: Can I use this in the browser?**
-A: Yes! Jira.js works in both Node.js and modern browsers. Make sure to handle CORS if calling Jira APIs from a browser.
+## Documentation
-**Q: How do I handle authentication?**
-A: Jira.js supports Basic Auth (email + API token) and OAuth 2.0. See the [Authentication](#authentication) section above.
+- [Getting started guide](https://jirajs.dev/guide/getting-started)
+- [Authentication](https://jirajs.dev/guide/authentication)
+- [Cloud API reference](https://jirajs.dev/cloud/)
+- [Agile API reference](https://jirajs.dev/agile/)
+- [Migration guide v0 → v1](https://jirajs.dev/migration/v0-to-v1)
+- [Examples](./examples/)
-## Other Products
+## Versioning
-Explore our other Atlassian integration libraries:
-- [Confluence.js](https://github.com/MrRefactoring/confluence.js) - Interact with Confluence API
-- [Trello.js](https://github.com/MrRefactoring/trello.js) - Trello API integration
+This project follows [semantic versioning](./SEMVER_POLICY.md). TypeScript-only breaking changes count as MAJOR.
-## Contributing
+## Security
-Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
+See [SECURITY.md](./SECURITY.md) for vulnerability reporting.
## License
-MIT License © MrRefactoring
-See [LICENSE](https://github.com/mrrefactoring/jira.js/blob/develop/LICENSE) for details.
+MIT
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 0000000000..094d3fbdb9
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,578 @@
+# TESTING.md
+
+## Overview
+
+This repository uses a layered testing strategy, with a special focus on **live API tests** for `packages/agile`.
+
+The goal of the test setup is to keep the SDK:
+
+- reliable
+- maintainable
+- safe to evolve
+- verified against the real Jira Cloud API where it matters
+
+This is an SDK repository, so tests should validate:
+
+- request construction
+- endpoint wiring
+- typed API contracts
+- real Jira compatibility
+
+---
+
+## Test Types
+
+### 1. Unit Tests
+
+Use unit tests for fast, isolated validation of SDK behavior.
+
+Examples:
+
+- request path construction
+- query parameter serialization
+- request body construction
+- client method wiring
+- helper behavior
+- edge-case parameter handling
+
+Unit tests should not require network access or a Jira tenant.
+
+These are the default and most important test layer.
+
+---
+
+### 2. Contract / Shape Tests
+
+Use contract tests to validate the package boundary and public SDK surface.
+
+Examples:
+
+- client exposes expected methods
+- endpoint methods are wired correctly
+- public exports remain stable
+- internal composition still matches expected API shape
+
+These help catch regressions when refactoring SDK internals.
+
+---
+
+### 3. Integration Tests
+
+Use integration tests only when needed to verify behavior across multiple internal layers.
+
+Examples:
+
+- feature package → base client request path
+- endpoint assembly + serialization + response normalization
+
+These should still avoid hitting the real Jira API unless there is a clear reason.
+
+---
+
+### 4. Live Tests
+
+Use live tests to validate the SDK against a **real Jira Cloud tenant**.
+
+Live tests are useful for verifying:
+
+- auth compatibility
+- real endpoint behavior
+- real request/response correctness
+- real-world Jira quirks that mocks do not catch
+
+Live tests should remain:
+
+- small
+- practical
+- opt-in
+- easy to maintain
+
+---
+
+## Current Live Test Scope
+
+At the moment, live tests are intended only for:
+
+- `packages/agile`
+
+Live tests are **not currently in scope** for:
+
+- `packages/cloud`
+- `packages/serviceDesk`
+
+Those packages are currently stubs and should not be included in testing work unless there is an explicit separate task.
+
+---
+
+## Recommended Test Folder Structure
+
+### For `packages/agile`
+
+```txt
+packages/agile/
+├── tests/
+│ ├── unit/
+│ ├── live/
+│ │ ├── helpers/
+│ │ ├── board.live.test.ts
+│ │ ├── sprint.live.test.ts
+│ │ ├── backlog.live.test.ts
+│ │ ├── boardProperties.live.test.ts
+│ │ └── issueMovement.live.test.ts
+│ └── shared/
+```
+
+### Folder purposes
+
+#### `tests/unit/`
+Use for:
+- endpoint wiring tests
+- request construction tests
+- public client tests
+- helper unit tests
+
+#### `tests/live/`
+Use for:
+- real Jira Cloud validation
+
+#### `tests/live/helpers/`
+Use for:
+- env handling
+- authenticated client setup
+- board/sprint/issue resolution
+- shared live test setup logic
+
+#### `tests/shared/`
+Use for:
+- shared non-live test utilities
+
+---
+
+## Environment Configuration
+
+Use a single `.env` file.
+
+### Minimal required config
+
+```env
+JIRA_BASE_URL=https://your-domain.atlassian.net
+JIRA_EMAIL=your-email@example.com
+JIRA_API_TOKEN=your_api_token_here
+```
+
+### Optional config
+
+```env
+JIRA_TEST_BOARD_ID=
+JIRA_TEST_PROJECT_KEY=
+```
+
+### Notes
+
+- `JIRA_TEST_BOARD_ID` is optional and can make board resolution faster
+- `JIRA_TEST_PROJECT_KEY` is optional and can help issue/sprint setup helpers
+- live tests should work with only the minimal required config whenever possible
+
+---
+
+## Running Tests
+
+### Default tests
+
+Run standard tests:
+
+```bash
+pnpm test
+```
+
+### Unit tests only
+
+```bash
+pnpm test:unit
+```
+
+### Live tests
+
+```bash
+pnpm test:live
+```
+
+---
+
+## Recommended Scripts
+
+### `packages/agile/package.json`
+
+```json
+{
+ "scripts": {
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:unit": "vitest run tests/unit",
+ "test:live": "vitest run tests/live"
+ }
+}
+```
+
+---
+
+## Live Test Rules
+
+### 1. Live tests must not run by default
+
+Live tests should only run when explicitly requested with:
+
+```bash
+pnpm test:live
+```
+
+They should not be part of the default fast test path.
+
+---
+
+### 2. Live tests must skip cleanly if env is missing
+
+If required env variables are missing:
+
+- skip the suite
+- do not fail the test run
+
+Use a shared helper such as:
+
+- `skipIfNoLiveEnv()`
+
+This makes local development and CI more predictable.
+
+---
+
+### 3. Do not hardcode tenant-specific IDs
+
+Avoid this:
+
+```ts
+const boardId = 12;
+const sprintId = 44;
+const issueKey = "SDK-123";
+```
+
+Prefer this:
+
+```ts
+const board = await resolveTestBoard(client, config);
+const sprint = await resolveOrCreateTestSprint(client, board.id);
+const issue = await resolveOrCreateTestIssue(config);
+```
+
+This keeps tests portable across Jira tenants.
+
+---
+
+### 4. Every live test should own its setup
+
+A live test should resolve or create the resources it needs.
+
+Do not assume:
+
+- a sprint already exists
+- a board property already exists
+- an issue is already in the right state
+
+Use helpers for setup instead.
+
+---
+
+### 5. Live tests must be independent
+
+Do not rely on test execution order.
+
+Avoid patterns like:
+
+- “this test uses the sprint created in the previous test”
+- “this test assumes the previous board property test already ran”
+
+Each test should be valid on its own.
+
+---
+
+### 6. Use unique names for created resources
+
+If a test creates a resource, use a unique prefix, for example:
+
+- `sdk-live-test-`
+- `sdk-live-test-`
+
+This prevents collisions and makes cleanup/debugging easier.
+
+---
+
+### 7. Read before mutate
+
+If a test performs a mutation:
+
+- first confirm the target resource exists
+- then perform the mutation
+- then verify the expected result
+
+This reduces noisy failures and improves diagnostics.
+
+---
+
+## Recommended Live Helpers
+
+Create shared helpers under:
+
+```txt
+packages/agile/tests/live/helpers/
+```
+
+### `liveEnv.ts`
+
+Responsible for:
+
+- reading `.env`
+- validating required values
+- exposing a normalized config object
+
+Suggested functions:
+
+- `getLiveEnv()`
+- `hasLiveEnv()`
+
+---
+
+### `createLiveAgileClient.ts`
+
+Responsible for:
+
+- creating an authenticated Agile client from env
+
+Suggested return shape:
+
+```ts
+{
+ client,
+ config: {
+ baseUrl,
+ email,
+ projectKey,
+ boardId,
+ }
+}
+```
+
+---
+
+### `skipIfNoLiveEnv.ts`
+
+Responsible for:
+
+- skipping live tests cleanly when env is incomplete
+
+---
+
+### `resolveTestBoard.ts`
+
+Responsible for:
+
+- finding a board for tests
+
+Recommended resolution order:
+
+1. use `JIRA_TEST_BOARD_ID` if provided
+2. otherwise discover a usable board dynamically
+3. otherwise skip/fail clearly
+
+---
+
+### `resolveOrCreateTestSprint.ts`
+
+Responsible for:
+
+- finding or creating a sprint for sprint-related tests
+
+---
+
+### `resolveOrCreateTestIssue.ts`
+
+Responsible for:
+
+- finding or creating a test issue for movement/ranking tests
+
+Important note:
+
+Issue creation may require using a non-agile Jira endpoint if `agile.jira.js` itself does not expose issue creation.
+That is acceptable as long as it is isolated to helper/setup code.
+
+---
+
+## Recommended First Live Test Files
+
+Start with these files:
+
+```txt
+packages/agile/tests/live/helpers/liveEnv.ts
+packages/agile/tests/live/helpers/createLiveAgileClient.ts
+packages/agile/tests/live/helpers/skipIfNoLiveEnv.ts
+packages/agile/tests/live/helpers/resolveTestBoard.ts
+packages/agile/tests/live/helpers/resolveOrCreateTestSprint.ts
+packages/agile/tests/live/helpers/resolveOrCreateTestIssue.ts
+packages/agile/tests/live/board.live.test.ts
+packages/agile/tests/live/sprint.live.test.ts
+packages/agile/tests/live/backlog.live.test.ts
+packages/agile/tests/live/boardProperties.live.test.ts
+```
+
+---
+
+## Recommended Live Test Order
+
+Implement in this order.
+
+### 1. `board.live.test.ts`
+
+Suggested coverage:
+
+- `getAllBoards`
+- `getBoard`
+- optionally `getConfiguration`
+
+Why start here:
+
+- validates auth
+- validates connectivity
+- validates board endpoint correctness
+- low maintenance
+
+---
+
+### 2. `sprint.live.test.ts`
+
+Suggested coverage:
+
+- `getAllSprints`
+- `getSprint`
+
+Why it matters:
+
+- validates board-scoped sprint retrieval
+- common Agile workflow coverage
+
+---
+
+### 3. `backlog.live.test.ts`
+
+Suggested coverage:
+
+- `getIssuesForBacklog`
+- `getIssuesForBoard`
+- optionally `getIssuesWithoutEpicForBoard`
+
+Why it matters:
+
+- issue listing is a core SDK use case
+- often exposes real Jira quirks
+
+---
+
+### 4. `boardProperties.live.test.ts`
+
+Suggested coverage:
+
+- `setBoardProperty`
+- `getBoardProperty`
+- `deleteBoardProperty`
+
+Why it matters:
+
+- excellent first write-oriented live test
+- low blast radius
+- easy cleanup
+
+---
+
+### 5. `issueMovement.live.test.ts`
+
+Suggested coverage:
+
+- `moveIssuesToSprintAndRank`
+- `moveIssuesToBacklog`
+- `rankIssues`
+
+Why it matters:
+
+- high-value Agile workflow coverage
+
+---
+
+## Anti-Flake Rules
+
+To keep the suite healthy:
+
+### Do
+
+- assert shape/presence rather than exact counts
+- resolve resources dynamically
+- use helper-driven setup
+- keep tests narrow
+- verify only the intended effect
+
+### Don’t
+
+- do not assert exact board counts
+- do not assume fixed issue ordering
+- do not depend on execution order
+- do not use shared production boards as test state
+- do not bundle too many mutations into one test
+
+---
+
+## CI Guidance
+
+### Default CI
+
+Run only:
+
+- unit tests
+- non-live integration tests
+
+### Live tests
+
+If live tests are added to CI later, they should run:
+
+- explicitly
+- on demand
+- nightly
+- before release
+
+Do not make live tests part of the default fast CI path.
+
+---
+
+## Contributor Guardrails
+
+### Do
+
+- preserve package boundaries
+- add tests for new endpoints where appropriate
+- keep live test setup simple and reusable
+- prefer maintainability over “perfect coverage”
+
+### Don’t
+
+- do not hardcode tenant-specific IDs
+- do not make live tests destructive by default
+- do not over-engineer the live test harness
+- do not include stub packages in testing work unless explicitly asked
+
+---
+
+## Final Recommendation
+
+Treat testing as a confidence tool, not a ceremony generator.
+
+The best test setup for this repo is:
+
+- simple
+- typed
+- predictable
+- focused on real value
diff --git a/commitlint.config.mjs b/commitlint.config.mjs
new file mode 100644
index 0000000000..ade2207922
--- /dev/null
+++ b/commitlint.config.mjs
@@ -0,0 +1,11 @@
+export default {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ 'scope-enum': [
+ 2,
+ 'always',
+ ['base', 'cloud', 'agile', 'ci', 'deps', 'release', 'docs', 'scripts'],
+ ],
+ 'body-max-line-length': [1, 'always', 120],
+ },
+};
diff --git a/consumers/node-cjs/smoke.cjs b/consumers/node-cjs/smoke.cjs
new file mode 100644
index 0000000000..526058f53b
--- /dev/null
+++ b/consumers/node-cjs/smoke.cjs
@@ -0,0 +1,47 @@
+'use strict';
+// CJS consumer smoke test — validates CommonJS require() semantics.
+// Installed and run by scripts/smoke-consumers.mjs against packed tarballs.
+const { createCloudClient } = require('@jira.js/cloud');
+const { createAgileClient } = require('@jira.js/agile');
+const { ApiError, createClient } = require('@jira.js/base');
+
+const checks = [];
+
+// Named exports resolve from CJS require()
+checks.push({ name: 'ApiError is a constructor', pass: typeof ApiError === 'function' });
+checks.push({ name: 'createClient is a function', pass: typeof createClient === 'function' });
+checks.push({ name: 'createCloudClient is a function', pass: typeof createCloudClient === 'function' });
+checks.push({ name: 'createAgileClient is a function', pass: typeof createAgileClient === 'function' });
+
+// ApiError behaves correctly under CJS
+const err = new ApiError('test', 500, 'Internal Server Error', { detail: 'boom' });
+checks.push({ name: 'ApiError.message is set', pass: err.message === 'test' });
+checks.push({ name: 'ApiError.status is set', pass: err.status === 500 });
+checks.push({ name: 'ApiError.body is set', pass: err.body !== null });
+checks.push({ name: 'ApiError instanceof Error', pass: err instanceof Error });
+
+// createCloudClient works under CJS
+const client = createCloudClient({ host: 'https://test.atlassian.net', auth: { email: 'x', apiToken: 'y' } });
+checks.push({ name: 'client.issues is an object', pass: typeof client.issues === 'object' });
+checks.push({ name: 'client.projects is an object', pass: typeof client.projects === 'object' });
+checks.push({ name: 'client.issues.createIssue is a function', pass: typeof client.issues.createIssue === 'function' });
+
+// CJS require returns the named exports object (no default wrapper)
+const cloudMod = require('@jira.js/cloud');
+checks.push({ name: 'require() returns named exports', pass: typeof cloudMod.createCloudClient === 'function' });
+
+const baseMod = require('@jira.js/base');
+checks.push({ name: 'base require() returns named exports', pass: typeof baseMod.ApiError === 'function' });
+
+const failed = checks.filter(c => !c.pass);
+
+for (const c of checks) {
+ console.log(` ${c.pass ? '✅' : '❌'} ${c.name}`);
+}
+
+if (failed.length > 0) {
+ console.error(`\n ${failed.length} check(s) failed`);
+ process.exit(1);
+}
+
+console.log(`\n All ${checks.length} CJS checks passed`);
diff --git a/consumers/node-esm/smoke.mjs b/consumers/node-esm/smoke.mjs
new file mode 100644
index 0000000000..d10dbd9343
--- /dev/null
+++ b/consumers/node-esm/smoke.mjs
@@ -0,0 +1,48 @@
+// ESM consumer smoke test — validates native ESM import semantics.
+// Installed and run by scripts/smoke-consumers.mjs against packed tarballs.
+import { createCloudClient } from '@jira.js/cloud';
+import { createAgileClient } from '@jira.js/agile';
+import { ApiError, createClient } from '@jira.js/base';
+
+const checks = [];
+
+// @jira.js/base — named exports are functions/classes
+checks.push({ name: 'ApiError is a constructor', pass: typeof ApiError === 'function' });
+checks.push({ name: 'createClient is a function', pass: typeof createClient === 'function' });
+
+// @jira.js/cloud — createCloudClient is a function
+checks.push({ name: 'createCloudClient is a function', pass: typeof createCloudClient === 'function' });
+
+// @jira.js/agile — createAgileClient is a function
+checks.push({ name: 'createAgileClient is a function', pass: typeof createAgileClient === 'function' });
+
+// ApiError is instantiable and extends Error
+const err = new ApiError('test', 404, 'Not Found', null);
+checks.push({ name: 'ApiError.message is set', pass: err.message === 'test' });
+checks.push({ name: 'ApiError.status is set', pass: err.status === 404 });
+checks.push({ name: 'ApiError instanceof Error', pass: err instanceof Error });
+checks.push({ name: 'ApiError instanceof ApiError', pass: err instanceof ApiError });
+
+// createCloudClient returns an object with expected namespaces
+const client = createCloudClient({ host: 'https://test.atlassian.net', auth: { email: 'x', apiToken: 'y' } });
+checks.push({ name: 'client.issues is an object', pass: typeof client.issues === 'object' });
+checks.push({ name: 'client.projects is an object', pass: typeof client.projects === 'object' });
+checks.push({ name: 'client.users is an object', pass: typeof client.users === 'object' });
+checks.push({ name: 'client.issues.getIssue is a function', pass: typeof client.issues.getIssue === 'function' });
+
+// No default export (named-only package)
+const cloudMod = await import('@jira.js/cloud');
+checks.push({ name: '@jira.js/cloud has no default export', pass: cloudMod.default === undefined });
+
+const failed = checks.filter(c => !c.pass);
+
+for (const c of checks) {
+ console.log(` ${c.pass ? '✅' : '❌'} ${c.name}`);
+}
+
+if (failed.length > 0) {
+ console.error(`\n ${failed.length} check(s) failed`);
+ process.exit(1);
+}
+
+console.log(`\n All ${checks.length} ESM checks passed`);
diff --git a/consumers/ts-bundler/smoke.ts b/consumers/ts-bundler/smoke.ts
new file mode 100644
index 0000000000..cf07b54929
--- /dev/null
+++ b/consumers/ts-bundler/smoke.ts
@@ -0,0 +1,58 @@
+// TypeScript bundler-resolution consumer smoke test.
+// Validates type inference under moduleResolution=bundler (Vite, esbuild, etc.).
+// This file is ONLY typechecked (tsc --noEmit), not executed.
+import { createCloudClient } from '@jira.js/cloud';
+import { createAgileClient } from '@jira.js/agile';
+import {
+ ApiError,
+ createClient,
+ type ClientConfig,
+ type Auth,
+ type HttpMethod,
+ type Client,
+} from '@jira.js/base';
+
+// moduleResolution=bundler allows extensionless imports (same as ESM bundlers do)
+const config: ClientConfig = {
+ host: 'https://test.atlassian.net',
+ auth: { type: 'basic', email: 'user@example.com', apiToken: 'token' } satisfies Extract,
+};
+
+// createClient type inference
+const baseClient: Client = createClient(config);
+
+// Generic type parameter flows through to return type
+async function genericPreservation(): Promise<{ id: string }> {
+ return baseClient.sendRequest<{ id: string }>({ url: '/rest/api/3/issue/PROJ-1' });
+}
+
+// cloudClient in bundler mode
+const cloudClient = createCloudClient(config);
+
+// Namespaces are present
+type IssuesType = typeof cloudClient.issues;
+type ProjectsType = typeof cloudClient.projects;
+
+// Method return types are Promise-based
+type GetIssueFn = IssuesType['getIssue'];
+type GetIssueReturn = ReturnType;
+type IsGetIssuePromise = GetIssueReturn extends Promise ? true : false;
+const _check: IsGetIssuePromise = true;
+
+// agileClient
+const agileClient = createAgileClient(config);
+type AllBoardsFn = typeof agileClient.board.getAllBoards;
+
+// HttpMethod is a literal union, not a wide string
+const validMethod: HttpMethod = 'GET';
+// @ts-expect-error — arbitrary string is not HttpMethod
+const invalidMethod: HttpMethod = 'SUBSCRIBE';
+
+// ApiError is throwable and its fields are typed
+function useApiError(): void {
+ throw new ApiError('Network error', 503, 'Service Unavailable', null);
+}
+
+void genericPreservation;
+void useApiError;
+void validMethod;
diff --git a/consumers/ts-bundler/tsconfig.json b/consumers/ts-bundler/tsconfig.json
new file mode 100644
index 0000000000..c2bb965329
--- /dev/null
+++ b/consumers/ts-bundler/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "strict": true,
+ "allowImportingTsExtensions": false,
+ "noUncheckedIndexedAccess": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "isolatedModules": true,
+ "verbatimModuleSyntax": true
+ },
+ "include": ["smoke.ts"]
+}
diff --git a/consumers/ts-strict/smoke.ts b/consumers/ts-strict/smoke.ts
new file mode 100644
index 0000000000..1b605e5fe6
--- /dev/null
+++ b/consumers/ts-strict/smoke.ts
@@ -0,0 +1,92 @@
+// TypeScript strict-mode consumer smoke test.
+// Validates type inference under strict + moduleResolution=node16.
+// This file is ONLY typechecked (tsc --noEmit), not executed.
+import { createCloudClient } from '@jira.js/cloud';
+import { createAgileClient } from '@jira.js/agile';
+import {
+ ApiError,
+ createClient,
+ type ClientConfig,
+ type Auth,
+ type HttpMethod,
+ type Client,
+ type SendRequestOptions,
+} from '@jira.js/base';
+
+// --- ClientConfig shape ---
+const config: ClientConfig = {
+ host: 'https://test.atlassian.net',
+ auth: { type: 'basic', email: 'user@example.com', apiToken: 'token' },
+};
+
+// --- createClient returns Client ---
+const baseClient: Client = createClient(config);
+
+// --- Client.sendRequest generic parameter is preserved ---
+async function checkGenericPreservation(): Promise {
+ const result: { id: string } = await baseClient.sendRequest<{ id: string }>({
+ url: '/rest/api/3/issue/PROJ-1',
+ method: 'GET' satisfies HttpMethod,
+ });
+ const _id: string = result.id;
+}
+
+// --- Auth variants ---
+const _basicAuth: Auth = { type: 'basic', email: 'user@example.com', apiToken: 'token' };
+const _bearerAuth: Auth = { type: 'bearer', token: 'jwt-token' };
+
+// --- ApiError is correctly typed ---
+function handleError(e: unknown): void {
+ if (e instanceof ApiError) {
+ const _status: number = e.status;
+ const _statusText: string = e.statusText;
+ const _body: unknown = e.body;
+ const _message: string = e.message;
+ }
+}
+
+// --- createCloudClient returns a typed client ---
+const cloudClient = createCloudClient(config);
+
+// Issues namespace
+async function checkIssues(): Promise {
+ const issue = await cloudClient.issues.getIssue({ issueIdOrKey: 'PROJ-1' });
+}
+
+async function checkTransitions(): Promise {
+ await cloudClient.issues.doTransition({ issueIdOrKey: 'PROJ-1', transition: { id: '31' } });
+}
+
+// Projects namespace
+async function checkProjects(): Promise {
+ const projects = await cloudClient.projects.searchProjects({});
+}
+
+// issueSearch namespace
+async function checkSearch(): Promise {
+ const results = await cloudClient.issueSearch.searchAndReconsileIssuesUsingJql({ jql: 'project = PROJ' });
+}
+
+// --- createAgileClient ---
+const agileClient = createAgileClient(config);
+
+async function checkAgile(): Promise {
+ const boards = await agileClient.board.getAllBoards({});
+}
+
+// --- HttpMethod rejects invalid values ---
+// @ts-expect-error — 'CONNECT' is not a valid HttpMethod
+const _badMethod: HttpMethod = 'CONNECT';
+
+// --- SendRequestOptions requires url ---
+// @ts-expect-error — url is required
+const _badOpts: SendRequestOptions = { method: 'GET' };
+
+// Suppress unused variable warnings
+void checkGenericPreservation;
+void handleError;
+void checkIssues;
+void checkTransitions;
+void checkProjects;
+void checkSearch;
+void checkAgile;
diff --git a/consumers/ts-strict/tsconfig.json b/consumers/ts-strict/tsconfig.json
new file mode 100644
index 0000000000..a28675f813
--- /dev/null
+++ b/consumers/ts-strict/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "isolatedModules": true
+ },
+ "include": ["smoke.ts"]
+}
diff --git a/context7.json b/context7.json
new file mode 100644
index 0000000000..30090d5d7b
--- /dev/null
+++ b/context7.json
@@ -0,0 +1,4 @@
+{
+ "url": "https://context7.com/mrrefactoring/jira.js",
+ "public_key": "pk_KmcNhVEfGYbntvK7UL6DZ"
+}
diff --git a/eslint.config.ts b/eslint.config.ts
index 98a774af74..c2d631cb46 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -33,11 +33,15 @@ export default defineConfig([
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/padding-line-between-statements': [
'error',
- // Return statements
- { blankLine: 'always', prev: '*', next: 'return' },
- // Import statements
{ blankLine: 'always', prev: 'import', next: '*' },
{ blankLine: 'any', prev: 'import', next: 'import' },
+ { blankLine: 'always', prev: 'export', next: 'export' },
+ { blankLine: 'always', prev: 'const', next: 'type' },
+ { blankLine: 'always', prev: '*', next: 'if' },
+ { blankLine: 'always', prev: 'if', next: '*' },
+ { blankLine: 'always', prev: '*', next: 'for' },
+ { blankLine: 'always', prev: 'for', next: '*' },
+ { blankLine: 'always', prev: '*', next: 'return' },
],
'@stylistic/quotes': ['error', 'single'],
'@stylistic/semi': ['error', 'always'],
diff --git a/examples/.editorconfig b/examples/.editorconfig
deleted file mode 100644
index ee9cffb8bd..0000000000
--- a/examples/.editorconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-root = true
-
-[*]
-indent_style = space
-indent_size = 2
-tab_width = 2
-end_of_line = lf
-charset = utf-8
-trim_trailing_whitespace = true
-insert_final_newline = true
-max_line_length = 120
diff --git a/examples/.gitignore b/examples/.gitignore
deleted file mode 100644
index 490df5cea1..0000000000
--- a/examples/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.idea/
-node_modules/
-src/credentials.ts
-
-.DS_Store
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index 34566f01d5..0000000000
--- a/examples/README.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Jira.js Usage Examples
-
-This guide provides examples of using the [`jira.js`](https://github.com/MrRefactoring/jira.js) library to interact with Jira's API. These examples will help you get started with common operations like creating a project, adding a task, adding a worklog, and retrieving all worklogs.
-
-## Table of contents
-
-1. [Getting Started](#getting-started)
-2. [Examples](#examples)
- * [Basic Example](#basic-example)
- * [Add Worklog Example](#add-worklog-example)
- * [Get All Worklogs Example](#get-all-worklogs-example)
-
-## Getting Started
-
-Before you start running the examples, make sure to complete the following steps:
-
-1. **Install Dependencies**: The examples require certain Node.js packages to run. Install these dependencies by running the command:
-
-```console
-npm i
-```
-
-2. **Setup Credentials:** The jira.js library uses your Jira's `host`, `email`, and `apiToken` to authenticate requests. Specify these in the `src/credentials.ts` file:
-
-```typescript
-const host = 'https://your-domain.atlassian.net';
-const email = 'YOUR_EMAIL';
-const apiToken = 'YOUR_API_TOKEN';
-```
-
-## Examples
-
-Here are some examples of what you can do with `jira.js`:
-
-### Basic Example
-
-This example creates a new project and a new task in that project.
-
-⚠️ **NOTE:** The script first checks if you have any existing projects.
-If you do, it creates a new task in the first project it finds.
-If you don't, it creates a new project with the key **PROJECT** and the name **My Project**,
-and then creates a task in that project.
-
-To run this example, use the command:
-
-```console
-npm run basic
-```
-
----
-
-### Add Worklog Example
-
-This example creates a new task in the first project it finds and adds a worklog to it.
-
-⚠️ **NOTE:** If you don't have any existing projects, you should run the [Basic Example](#basic-example) first to create a project.
-One new Worklog will be added.
-
-To run this example, use the command:
-
-```console
-npm run addWorklog
-```
-
----
-
-### Get All Worklogs Example
-
-This example creates a new task, adds a worklog to it,
-and then retrieves all the worklogs that have been added to the task.
-
-⚠️ **NOTE:** Similar to the Add Worklog Example, you should have an existing project before running this example.
-If you don't, run the Basic Example first.
-
-To run this example, use the command:
-
-```console
-npm run getAllWorklogs
-```
diff --git a/examples/package-lock.json b/examples/package-lock.json
deleted file mode 100644
index d2c23c991e..0000000000
--- a/examples/package-lock.json
+++ /dev/null
@@ -1,355 +0,0 @@
-{
- "name": "jira.js-examples",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "jira.js-examples",
- "version": "1.0.0",
- "license": "MIT",
- "dependencies": {
- "jira.js": "latest"
- },
- "devDependencies": {
- "@types/node": "^22.5.4",
- "ts-node": "^10.9.2",
- "typescript": "^5.5.4"
- }
- },
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
- }
- },
- "node_modules/@tsconfig/node10": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
- "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tsconfig/node16": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "22.5.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
- "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.19.2"
- }
- },
- "node_modules/acorn": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
- "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-walk": {
- "version": "8.3.4",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "acorn": "^8.11.0"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/arg": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/axios": {
- "version": "1.7.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/create-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/jira.js": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/jira.js/-/jira.js-4.0.1.tgz",
- "integrity": "sha512-2zf8LozW9rgx5wgTdGSJMhUXDK1g8a/ngm1xDWnREX/h8kuBhNkMro4XELA2XRVvaNTbRMIK3PBgOvWFDddhIw==",
- "license": "MIT",
- "dependencies": {
- "axios": "^1.7.2",
- "form-data": "^4.0.0",
- "tslib": "^2.6.3"
- }
- },
- "node_modules/make-error": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/ts-node": {
- "version": "10.9.2",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
- "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
- "bin": {
- "ts-node": "dist/bin.js",
- "ts-node-cwd": "dist/bin-cwd.js",
- "ts-node-esm": "dist/bin-esm.js",
- "ts-node-script": "dist/bin-script.js",
- "ts-node-transpile-only": "dist/bin-transpile.js",
- "ts-script": "dist/bin-script-deprecated.js"
- },
- "peerDependencies": {
- "@swc/core": ">=1.2.50",
- "@swc/wasm": ">=1.2.50",
- "@types/node": "*",
- "typescript": ">=2.7"
- },
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "@swc/wasm": {
- "optional": true
- }
- }
- },
- "node_modules/tslib": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
- "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
- "license": "0BSD"
- },
- "node_modules/typescript": {
- "version": "5.5.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
- "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/yn": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- }
- }
-}
diff --git a/examples/package.json b/examples/package.json
deleted file mode 100644
index d0746d51c5..0000000000
--- a/examples/package.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "jira.js-examples",
- "private": true,
- "version": "1.0.0",
- "description": "",
- "author": "Vladislav Tupikin ",
- "scripts": {
- "basic": "ts-node src/basic.ts",
- "addWorklog": "ts-node src/addWorklog.ts",
- "getAllWorklogs": "ts-node src/getAllWorklogs.ts",
- "addFixVersion": "ts-node src/addFixVersion.ts"
- },
- "license": "MIT",
- "devDependencies": {
- "@types/node": "^22.5.4",
- "ts-node": "^10.9.2",
- "typescript": "^5.5.4"
- },
- "dependencies": {
- "jira.js": "latest"
- }
-}
diff --git a/examples/src/addFixVersion.ts b/examples/src/addFixVersion.ts
deleted file mode 100644
index 2803ade7f2..0000000000
--- a/examples/src/addFixVersion.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Version3Client } from 'jira.js';
-import { createIssue } from './utils';
-import { apiToken, email, host } from './credentials';
-
-async function addFixVersion() {
- const client = new Version3Client({
- host,
- authentication: {
- basic: { email, apiToken },
- },
- });
-
- const { id: issueIdOrKey } = await createIssue(client);
-
- const fix = await client.issueProperties.setIssueProperty({
- issueIdOrKey,
- propertyKey: 'fixVersion',
- propertyValue: 'N/a',
- });
-
- console.log(fix);
-}
-
-addFixVersion().catch(e => {
- console.error(e);
-
- throw new Error(e.errorMessages?.join(' '));
-});
diff --git a/examples/src/addWorklog.ts b/examples/src/addWorklog.ts
deleted file mode 100644
index 2b9fefc932..0000000000
--- a/examples/src/addWorklog.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Version3Client } from 'jira.js';
-import { createIssue } from './utils';
-import { apiToken, email, host } from './credentials';
-
-async function addWorklog() {
- const client = new Version3Client({
- host,
- authentication: {
- basic: { email, apiToken },
- },
- });
-
- // Used to reduce the amount of code that is not directly related to creating a worklog
- const { id: issueIdOrKey } = await createIssue(client);
-
- // The main part responsible for creating the worklog
- const worklog = await client.issueWorklogs.addWorklog({
- issueIdOrKey, // Required
- comment: 'My first worklog', // Not requited
- timeSpentSeconds: 60, // Required one of `timeSpentSeconds` or `timeSpent`
- });
-
- console.log(`Worklog successfully added for Issue Id: ${worklog.issueId}`);
-}
-
-addWorklog().catch(e => {
- console.error(e);
-
- throw new Error(e.errorMessages.join(' '));
-});
diff --git a/examples/src/basic.ts b/examples/src/basic.ts
deleted file mode 100644
index 0905c3a07e..0000000000
--- a/examples/src/basic.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Version3Client } from 'jira.js';
-import { apiToken, email, host } from './credentials';
-
-const client = new Version3Client({
- host,
- authentication: {
- basic: { email, apiToken },
- },
-});
-
-async function main() {
- const { values: projects } = await client.projects.searchProjects();
-
- if (projects.length) {
- const project = projects[0];
-
- const { id } = await client.issues.createIssue({
- fields: {
- summary: 'My first issue',
- issuetype: {
- name: 'Task',
- },
- project: {
- key: project.key,
- },
- },
- });
-
- const issue = await client.issues.getIssue({ issueIdOrKey: id });
-
- console.log(`Issue '${issue.fields.summary}' was successfully added to '${project.name}' project.`);
- } else {
- const myself = await client.myself.getCurrentUser();
-
- const { id } = await client.projects.createProject({
- key: 'PROJECT',
- name: 'My Project',
- leadAccountId: myself.accountId,
- projectTypeKey: 'software',
- });
-
- const project = await client.projects.getProject({ projectIdOrKey: id.toString() });
-
- console.log(`Project '${project.name}' was successfully created.`);
- }
-}
-
-main()
- .catch(e => {
- console.error(e);
-
- throw new Error(JSON.stringify(e));
- });
diff --git a/examples/src/credentials.ts b/examples/src/credentials.ts
deleted file mode 100644
index 64b7a7a9bd..0000000000
--- a/examples/src/credentials.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export const host = '';
-export const email = '';
-export const apiToken = '';
-
-if (!host) {
- throw new Error('Please specify host');
-} else if (!email) {
- throw new Error('Please specify email');
-} else if (!apiToken) {
- throw new Error('Please specify apiToken');
-}
diff --git a/examples/src/getAllWorklogs.ts b/examples/src/getAllWorklogs.ts
deleted file mode 100644
index 04d4bc49d8..0000000000
--- a/examples/src/getAllWorklogs.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Version3Client } from 'jira.js';
-import { addWorklog, createIssue } from './utils';
-import { apiToken, email, host } from './credentials';
-
-async function getAllWorklogs() {
- const client = new Version3Client({
- host,
- authentication: {
- basic: { email, apiToken },
- },
- });
-
- // Used to reduce the amount of code that is not directly related to getting a worklogs
- const issue = await createIssue(client);
-
- // Let's add some worklogs
- await addWorklog(client, issue);
- await addWorklog(client, issue);
- await addWorklog(client, issue);
-
- // The main part responsible for getting the worklogs
- const worklogs = [];
-
- let offset = 0;
- let total = 0;
-
- do {
- const worklogsPaginated = await client.issueWorklogs.getIssueWorklog({ issueIdOrKey: issue.key, startAt: offset });
-
- offset += worklogsPaginated.worklogs.length;
- total = worklogsPaginated.total;
- worklogs.push(...worklogsPaginated.worklogs);
- } while (offset < total);
-
- console.log(`Received ${worklogs.length} worklogs.`);
-}
-
-getAllWorklogs().catch(e => {
- console.error(e);
-
- throw new Error(e.errorMessages.join(' '));
-});
diff --git a/examples/src/utils/addWorklog.ts b/examples/src/utils/addWorklog.ts
deleted file mode 100644
index fb0bae502f..0000000000
--- a/examples/src/utils/addWorklog.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Version3Client, Version3Models } from 'jira.js';
-
-export const addWorklog = async (client: Version3Client, issue: Version3Models.Issue) => {
- await client.issueWorklogs.addWorklog({
- issueIdOrKey: issue.id,
- comment: 'My first worklog',
- timeSpentSeconds: 60,
- });
-};
diff --git a/examples/src/utils/createIssue.ts b/examples/src/utils/createIssue.ts
deleted file mode 100644
index 1e37f20eef..0000000000
--- a/examples/src/utils/createIssue.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Version3Client } from 'jira.js';
-
-export const createIssue = async (client: Version3Client) => {
- const { values: projects } = await client.projects.searchProjects();
-
- if (projects.length) {
- const { key } = projects[0];
-
- const { id } = await client.issues.createIssue({
- fields: {
- summary: 'My first issue',
- issuetype: {
- name: 'Task',
- },
- project: {
- key,
- },
- },
- });
-
- return client.issues.getIssue({ issueIdOrKey: id });
- }
-
- throw new Error('First create a project');
-};
diff --git a/examples/src/utils/index.ts b/examples/src/utils/index.ts
deleted file mode 100644
index f229bb1407..0000000000
--- a/examples/src/utils/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './addWorklog';
-export * from './createIssue';
diff --git a/package.json b/package.json
index 9ec03f940a..11b8870ff7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "jira.js",
- "version": "5.3.1",
+ "version": "6.0.0",
"description": "Modern Jira REST API client for JavaScript and TypeScript. Full-featured library for Jira Cloud API v2/v3, Jira Agile API, and Jira Service Desk API. Works in Node.js and browsers with TypeScript support, tree-shaking, and comprehensive type definitions.",
"repository": {
"type": "git",
@@ -12,173 +12,72 @@
},
"author": "Vladislav Tupikin ",
"license": "MIT",
- "sideEffects": false,
"type": "module",
- "types": "./dist/esm/types/index.d.ts",
- "module": "./dist/esm/index.mjs",
- "main": "./dist/cjs/index.cjs",
- "exports": {
- ".": {
- "types": "./dist/esm/types/index.d.ts",
- "require": "./dist/cjs/index.cjs",
- "import": "./dist/esm/index.mjs"
- },
- "./*": {
- "types": "./dist/esm/types/*.d.ts",
- "require": "./dist/cjs/*.cjs",
- "import": "./dist/esm/*.mjs"
- },
- "./agile": {
- "types": "./dist/esm/types/agile/index.d.ts",
- "require": "./dist/cjs/agile/index.cjs",
- "import": "./dist/esm/agile/index.mjs"
- },
- "./serviceDesk": {
- "types": "./dist/esm/types/serviceDesk/index.d.ts",
- "require": "./dist/cjs/serviceDesk/index.cjs",
- "import": "./dist/esm/serviceDesk/index.mjs"
- },
- "./version2": {
- "types": "./dist/esm/types/version2/index.d.ts",
- "require": "./dist/cjs/version2/index.cjs",
- "import": "./dist/esm/version2/index.mjs"
- },
- "./version3": {
- "types": "./dist/esm/types/version3/index.d.ts",
- "require": "./dist/cjs/version3/index.cjs",
- "import": "./dist/esm/version3/index.mjs"
- },
- "./package.json": "./package.json"
- },
- "keywords": [
- "jira",
- "jira-api",
- "jira api",
- "jira rest api",
- "jira cloud",
- "jira cloud api",
- "jira v3",
- "jira v2",
- "jira agile",
- "jira service desk",
- "jira software",
- "jira client",
- "jira sdk",
- "jira integration",
- "jira automation",
- "jira webhook",
- "jira webhooks",
- "jira issues",
- "jira projects",
- "jira workflow",
- "jira automation tool",
- "atlassian",
- "atlassian jira",
- "atlassian api",
- "atlassian cloud",
- "atlassian integration",
- "javascript",
- "typescript",
- "ts",
- "node",
- "nodejs",
- "node.js",
- "browser",
- "rest",
- "rest api",
- "restful",
- "api client",
- "http client",
- "axios",
- "esm",
- "es modules",
- "cjs",
- "commonjs",
- "typescript library",
- "type definitions",
- "type-safe",
- "tree shaking",
- "npm package"
- ],
+ "sideEffects": false,
"engines": {
- "node": ">=20"
+ "node": ">=22"
},
"scripts": {
- "build": "pnpm run build:src && pnpm run build:tests",
- "build:src": "rollup -c rollup.config.ts --configPlugin typescript",
- "build:tests": "tsc --project tests/tsconfig.json",
- "prettier": "prettier --write src",
- "lint": "pnpm run lint:tests && pnpm run lint:src:agile && pnpm run lint:src:clients && pnpm run lint:src:services && pnpm run lint:src:version2 && pnpm run lint:src:version3 && pnpm run lint:src:files",
- "lint:tests": "pnpm run lint:base tests",
- "lint:src:agile": "pnpm run lint:base src/agile",
- "lint:src:clients": "pnpm run lint:base src/clients",
- "lint:src:services": "pnpm run lint:base src/services",
- "lint:src:version2": "pnpm run lint:base src/version2",
- "lint:src:version3": "pnpm run lint:base src/version3",
- "lint:src:serviceDesk": "pnpm run lint:base src/serviceDesk",
- "lint:src:files": "pnpm run lint:base src/*.ts",
- "lint:base": "eslint --ext .ts",
- "lint:fix": "pnpm run lint:tests --fix && pnpm run lint:src:agile --fix && pnpm run lint:src:clients --fix && pnpm run lint:src:services --fix && pnpm run lint:src:version2 --fix && pnpm run lint:src:version3 --fix && pnpm run lint:src:serviceDesk --fix && pnpm run lint:src:files --fix",
- "doc": "typedoc --name \"Jira.js - Jira Cloud API library\" --out docs ./src/index.ts --favicon https://bad37fb3-cb50-4e0b-9035-a3e09e8afb3b.selstorage.ru/jira.js%2Ffavicon.svg",
- "test": "pnpm run build:tests && pnpm run test:unit && pnpm run test:integration",
- "test:unit": "pnpm run build:src && vitest run tests/unit --sequence.concurrent",
- "test:integration": "VITEST_MODE=integration vitest run tests/integration --bail=1 --no-file-parallelism --max-concurrency 1 --hookTimeout 100000 --testTimeout 100000",
- "replace:all": "pnpm run replace:fixExpansionMarkup && pnpm run replace:permissions:version2 && pnpm run replace:permissions:version3 && pnpm run replace:pagination:version2 && pnpm run replace:pagination:version3 && pnpm run replace:async:version2 && pnpm run replace:async:version3 && pnpm run replace:expansion:version2 && pnpm run replace:expansion:version3 && pnpm run replace:ordering:version2 && pnpm run replace:ordering:version3 && pnpm run replace:groupMember:version2 && pnpm run replace:workflowPaginated:version2 && pnpm run replace:attachment:serviceDesk && pnpm run replace:priority:version3 && pnpm run replace:projectAvatar:version3 && pnpm run replace:issueType:version3 && pnpm run replace:issueType:version2 && pnpm run replace:projectAvatar:version2 && pnpm run replace:priority:version2 && pnpm run replace:projectCreate:agile && pnpm run replace:filterCreate:agile",
- "replace:permissions:version2": "grep -rl \"(#permissions)\" ./src/version2 | xargs sed -i '' 's/(#permissions)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/intro\\/#permissions)/g'",
- "replace:permissions:version3": "grep -rl \"(#permissions)\" ./src/version3 | xargs sed -i '' 's/(#permissions)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/intro\\/#permissions)/g'",
- "replace:pagination:version2": "grep -rl \"(#pagination)\" ./src/version2 | xargs sed -i '' 's/(#pagination)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/intro\\/#pagination)/g'",
- "replace:pagination:version3": "grep -rl \"(#pagination)\" ./src/version3 | xargs sed -i '' 's/(#pagination)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/intro\\/#pagination)/g'",
- "replace:async:version2": "grep -rl \"(#async)\" ./src/version2 | xargs sed -i '' 's/(#async)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/intro\\/#async-operations)/g'",
- "replace:async:version3": "grep -rl \"(#async)\" ./src/version3 | xargs sed -i '' 's/(#async)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/intro\\/#async-operations)/g'",
- "replace:expansion:version2": "grep -rl \"(#expansion)\" ./src/version2 | xargs sed -i '' 's/(#expansion)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/intro\\/#expansion)/g'",
- "replace:expansion:version3": "grep -rl \"(#expansion)\" ./src/version3 | xargs sed -i '' 's/(#expansion)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/intro\\/#expansion)/g'",
- "replace:ordering:version2": "grep -rl \"(#ordering)\" ./src/version2 | xargs sed -i '' 's/(#ordering)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/intro\\/#ordering)/g'",
- "replace:ordering:version3": "grep -rl \"(#ordering)\" ./src/version3 | xargs sed -i '' 's/(#ordering)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/intro\\/#ordering)/g'",
- "replace:groupMember:version2": "grep -rl \"(#api-rest-api-2-group-member-get)\" ./src/version2 | xargs sed -i '' 's/(#api-rest-api-2-group-member-get)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/api-group-groups\\/#api-rest-api-2-group-member-get)/g'",
- "replace:workflowPaginated:version2": "grep -rl \"(#api-rest-api-2-workflow-search-get)\" ./src/version2 | xargs sed -i '' 's/(#api-rest-api-2-workflow-search-get)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/api-group-workflows\\/#api-rest-api-2-workflow-search-get)/g'",
- "replace:attachment:serviceDesk": "grep -rl \"(#api-request-issueIdOrKey-attachment-post)\" ./src/serviceDesk | xargs sed -i '' 's/(#api-request-issueIdOrKey-attachment-post)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/service-desk\\/rest\\/api-group-servicedesk\\/#api-rest-servicedeskapi-servicedesk-servicedeskid-attachtemporaryfile-post)/g'",
- "replace:priority:version3": "grep -rl \"(#api-rest-api-3-priority-id-put)\" ./src/version3 | xargs sed -i '' 's/(#api-rest-api-3-priority-id-put)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/api-group-issue-priorities\\/#api-rest-api-3-priority-id-put)/g'",
- "replace:projectAvatar:version3": "grep -rl \"(#api-rest-api-3-project-projectIdOrKey-avatar-put)\" ./src/version3 | xargs sed -i '' 's/(#api-rest-api-3-project-projectIdOrKey-avatar-put)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/api-group-project-avatars\\/#api-rest-api-3-project-projectidorkey-avatar-put)/g'",
- "replace:issueType:version3": "grep -rl \"(#api-rest-api-3-issuetype-id-put)\" ./src/version3 | xargs sed -i '' 's/(#api-rest-api-3-issuetype-id-put)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/api-group-issue-types\\/#api-rest-api-3-issuetype-id-put)/g'",
- "replace:issueType:version2": "grep -rl \"(#api-rest-api-2-issuetype-id-put)\" ./src/version2 | xargs sed -i '' 's/(#api-rest-api-2-issuetype-id-put)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/api-group-issue-types\\/#api-rest-api-2-issuetype-id-put)/g'",
- "replace:projectAvatar:version2": "grep -rl \"(#api-rest-api-2-project-projectIdOrKey-avatar-put)\" ./src/version2 | xargs sed -i '' 's/(#api-rest-api-2-project-projectIdOrKey-avatar-put)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/api-group-project-avatars\\/#api-rest-api-2-project-projectidorkey-avatar-put)/g'",
- "replace:priority:version2": "grep -rl \"(#api-rest-api-2-priority-id-put)\" ./src/version2 | xargs sed -i '' 's/(#api-rest-api-2-priority-id-put)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/api-group-issue-priorities\\/#api-rest-api-2-priority-id-put)/g'",
- "replace:projectCreate:agile": "grep -rl \"(#api-rest-api-3-project-post)\" ./src/agile | xargs sed -i '' 's/(#api-rest-api-3-project-post)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/api-group-projects\\/#api-rest-api-3-project-post)/g'",
- "replace:filterCreate:agile": "grep -rl \"(#api-rest-api-3-filter-post)\" ./src/agile | xargs sed -i '' 's/(#api-rest-api-3-filter-post)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/api-group-filters\\/#api-rest-api-3-filter-post)/g'",
- "replace:fixExpansionMarkup": "grep -rl \"(em>#expansion)\" ./src | xargs sed -i '' 's/(em>#expansion)/(#expansion)/g'",
- "replace:fixCodeBlockSemicolons": "grep -rl '```;' ./src | xargs sed -i '' 's/```;/```/g'",
- "code:formatting": "pnpm run replace:all && pnpm run prettier && pnpm run lint:fix && pnpm run replace:fixCodeBlockSemicolons"
- },
- "dependencies": {
- "axios": "^1.13.5",
- "mime-types": "^2.1.35",
- "zod": "^4.3.6"
+ "test": "vitest run",
+ "test:coverage": "vitest run --coverage",
+ "build": "pnpm -r --filter './packages/*' run build",
+ "prettier": "prettier --write packages",
+ "lint": "eslint --ext .ts packages/*/src eslint.config.ts",
+ "lint:fix": "pnpm run lint --fix",
+ "code:formatting": "pnpm run prettier && pnpm run lint:fix",
+ "docs:dev": "vitepress dev docs",
+ "docs:build": "vitepress build docs",
+ "docs:preview": "vitepress preview docs",
+ "docs:api": "typedoc",
+ "api:surface:check": "node scripts/api-surface.mjs check",
+ "api:surface:update": "node scripts/api-surface.mjs update",
+ "api:exports:check": "node scripts/check-exports.mjs",
+ "api:governance": "node scripts/check-exports.mjs && node scripts/api-surface.mjs check",
+ "release:pack": "node scripts/pack-validate.mjs",
+ "release:smoke": "node scripts/smoke-consumers.mjs",
+ "release:attw": "node scripts/attw-check.mjs",
+ "release:size": "node scripts/size-check.mjs",
+ "release:size:update": "node scripts/size-check.mjs --update-baseline",
+ "release:cert": "node scripts/release-cert.mjs",
+ "release:cert:fast": "node scripts/release-cert.mjs --skip-consumers",
+ "release:score": "node scripts/release-score.mjs",
+ "release:score:fast": "node scripts/release-score.mjs --fast",
+ "release:diff": "node scripts/api-diff.mjs",
+ "release:history": "node scripts/compat-history.mjs show",
+ "release:history:record": "node scripts/compat-history.mjs record",
+ "changeset": "changeset",
+ "changeset:version": "changeset version",
+ "changeset:status": "changeset status",
+ "release:publish": "pnpm publish -r --no-git-checks --provenance",
+ "prepare": "husky"
},
"devDependencies": {
+ "@arethetypeswrong/cli": "^0.18.2",
+ "@changesets/cli": "^2.31.0",
+ "@commitlint/cli": "^21.0.1",
+ "@commitlint/config-conventional": "^21.0.1",
"@eslint/js": "^10.0.1",
- "@rollup/plugin-alias": "^6.0.0",
- "@rollup/plugin-commonjs": "^29.0.0",
- "@rollup/plugin-node-resolve": "^16.0.3",
- "@rollup/plugin-typescript": "^12.3.0",
- "@stylistic/eslint-plugin": "^5.8.0",
+ "@stylistic/eslint-plugin": "^5.10.0",
"@types/mime-types": "^3.0.1",
- "@types/node": "^20.19.33",
- "@types/sinon": "^21.0.0",
- "dotenv": "^17.3.1",
- "eslint": "^10.0.0",
- "globals": "^17.3.0",
- "jiti": "^2.6.1",
- "prettier": "^3.8.1",
+ "@types/node": "^22.19.19",
+ "@vitest/coverage-v8": "^4.1.6",
+ "eslint": "^10.3.0",
+ "fast-check": "^4.8.0",
+ "globals": "^17.6.0",
+ "husky": "^9.1.7",
+ "jiti": "^2.7.0",
+ "prettier": "^3.8.3",
"prettier-plugin-jsdoc": "^1.8.0",
- "rollup": "^4.57.1",
- "rollup-plugin-esnext-to-nodenext": "^1.0.1",
- "rollup-plugin-node-externals": "^8.1.2",
- "sinon": "^21.0.1",
- "tslib": "^2.8.1",
- "typedoc": "^0.28.17",
- "typescript": "^5.9.3",
- "typescript-eslint": "^8.56.0",
- "vitest": "^4.0.18"
+ "rollup": "^4.60.3",
+ "rollup-plugin-dts": "^6.4.1",
+ "typedoc": "^0.28.19",
+ "typedoc-plugin-markdown": "^4.11.0",
+ "typescript": "^6.0.3",
+ "typescript-eslint": "^8.59.3",
+ "vite": "8.0.12",
+ "vite-plugin-externalize-deps": "^0.10.0",
+ "vitepress": "^1.6.4",
+ "vitest": "^4.1.6"
},
- "packageManager": "pnpm@10.30.0"
+ "packageManager": "pnpm@11.1.1"
}
diff --git a/packages/agile/README.md b/packages/agile/README.md
new file mode 100644
index 0000000000..883d88db30
--- /dev/null
+++ b/packages/agile/README.md
@@ -0,0 +1,62 @@
+# @jira.js/agile
+
+TypeScript client for the Jira Agile REST API — boards, sprints, epics, backlogs.
+
+[](https://www.npmjs.com/package/@jira.js/agile)
+[](https://nodejs.org/)
+
+## Install
+
+```bash
+pnpm add @jira.js/agile
+```
+
+## Quick start
+
+```typescript
+import { createAgileClient } from '@jira.js/agile';
+
+const agile = createAgileClient({
+ host: 'https://your-domain.atlassian.net',
+ auth: { type: 'basic', email: 'you@example.com', apiToken: 'TOKEN' },
+});
+
+const boards = await agile.board.getAllBoards({ type: 'scrum' });
+const activeSprints = await agile.sprint.getAllSprints({ boardId: boards.values![0].id!, state: 'active' });
+```
+
+## Requirements
+
+- Node.js >= 22
+- TypeScript >= 6
+- `moduleResolution: Bundler` or `NodeNext`
+
+## Namespaces
+
+13 namespaces: `board`, `sprint`, `backlog`, `epic`, `issue`, `builds`, `deployments`, `developmentInformation`, `devopsComponents`, `featureFlags`, `operations`, `remoteLinks`, `securityInformation`.
+
+See the [full namespace reference](https://jirajs.dev/agile/).
+
+## Authentication
+
+Same as `@jira.js/cloud` — see [authentication guide](https://jirajs.dev/guide/authentication).
+
+## TypeScript type exports
+
+```typescript
+import type { AgileClient } from '@jira.js/agile';
+
+function processBoard(client: AgileClient, boardId: number) {
+ return client.board.getBoard({ boardId });
+}
+```
+
+## Links
+
+- [Documentation](https://jirajs.dev)
+- [Agile API reference](https://jirajs.dev/agile/)
+- [GitHub](https://github.com/MrRefactoring/jira.js)
+
+## License
+
+MIT
diff --git a/packages/agile/api-surface.snap b/packages/agile/api-surface.snap
new file mode 100644
index 0000000000..b959bc143c
--- /dev/null
+++ b/packages/agile/api-surface.snap
@@ -0,0 +1,9 @@
+# @jira.js/agile — public API surface snapshot
+# DO NOT EDIT manually. Update via: node scripts/api-surface.mjs update
+# To verify: node scripts/api-surface.mjs check
+
+## Values
+ createAgileClient
+
+## Types
+ AgileClient
diff --git a/packages/agile/package.json b/packages/agile/package.json
new file mode 100644
index 0000000000..8bd39ec0d0
--- /dev/null
+++ b/packages/agile/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@jira.js/agile",
+ "version": "0.0.1",
+ "description": "Jira Agile REST API client",
+ "type": "module",
+ "sideEffects": false,
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ },
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ }
+ },
+ "./package.json": "./package.json"
+ },
+ "imports": {
+ "#/*": "./dist/*"
+ },
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc --noEmit && vite build && tsc --emitDeclarationOnly && node ../../scripts/bundle-dts.mjs",
+ "test": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && vitest run",
+ "test:watch": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && vitest",
+ "test:unit": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && vitest run tests/unit",
+ "test:live": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && vitest run tests/live",
+ "test:coverage": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && vitest run --coverage"
+ },
+ "dependencies": {
+ "@jira.js/base": "workspace:*",
+ "@jira.js/cloud": "workspace:*",
+ "zod": "^4.4.3"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+}
diff --git a/packages/agile/src/api/backlog.ts b/packages/agile/src/api/backlog.ts
new file mode 100644
index 0000000000..b93d968559
--- /dev/null
+++ b/packages/agile/src/api/backlog.ts
@@ -0,0 +1,44 @@
+import { type MoveIssuesToBacklog } from '#/parameters/moveIssuesToBacklog';
+import { type MoveIssuesToBacklogForBoard } from '#/parameters/moveIssuesToBacklogForBoard';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Move issues to the backlog.\
+ * This operation is equivalent to remove future and active sprints from a given set of issues. At most 50 issues may be
+ * moved at once.
+ */
+export async function moveIssuesToBacklog(client: Client, parameters: MoveIssuesToBacklog): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/backlog/issue',
+ method: 'POST',
+ body: {
+ issues: parameters.issues,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Move issues to the backlog of a particular board (if they are already on that board).\
+ * This operation is equivalent to remove future and active sprints from a given set of issues if the board has sprints
+ * If the board does not have sprints this will put the issues back into the backlog from the board. At most 50 issues
+ * may be moved at once.
+ */
+export async function moveIssuesToBacklogForBoard(
+ client: Client,
+ parameters: MoveIssuesToBacklogForBoard,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/backlog/${parameters.boardId}/issue`,
+ method: 'POST',
+ body: {
+ issues: parameters.issues,
+ rankAfterIssue: parameters.rankAfterIssue,
+ rankBeforeIssue: parameters.rankBeforeIssue,
+ rankCustomFieldId: parameters.rankCustomFieldId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/board.ts b/packages/agile/src/api/board.ts
new file mode 100644
index 0000000000..ac0ac0f9a9
--- /dev/null
+++ b/packages/agile/src/api/board.ts
@@ -0,0 +1,580 @@
+import { GetAllBoardsSchema, type GetAllBoards } from '#/models/getAllBoards';
+import { CreateBoardSchema, type CreateBoard } from '#/models/createBoard';
+import { GetBoardByFilterIdSchema, type GetBoardByFilterId } from '#/models/getBoardByFilterId';
+import { GetBoardSchema, type GetBoard } from '#/models/getBoard';
+import { SearchResultsSchema, type SearchResults } from '#/models/searchResults';
+import { GetConfigurationSchema, type GetConfiguration } from '#/models/getConfiguration';
+import { GetEpicsSchema, type GetEpics } from '#/models/getEpics';
+import { GetFeaturesForBoardSchema, type GetFeaturesForBoard } from '#/models/getFeaturesForBoard';
+import { ToggleFeaturesSchema, type ToggleFeatures } from '#/models/toggleFeatures';
+import { GetProjectsSchema, type GetProjects } from '#/models/getProjects';
+import { GetProjectsFullSchema, type GetProjectsFull } from '#/models/getProjectsFull';
+import { PropertyKeysSchema, type PropertyKeys } from '#/models/propertyKeys';
+import { EntityPropertySchema, type EntityProperty } from '#/models/entityProperty';
+import { GetAllQuickFiltersSchema, type GetAllQuickFilters } from '#/models/getAllQuickFilters';
+import { GetQuickFilterSchema, type GetQuickFilter } from '#/models/getQuickFilter';
+import { GetReportsForBoardSchema, type GetReportsForBoard } from '#/models/getReportsForBoard';
+import { GetAllSprintsSchema, type GetAllSprints } from '#/models/getAllSprints';
+import { GetAllVersionsSchema, type GetAllVersions } from '#/models/getAllVersions';
+import { type GetAllBoards as GetAllBoardsParameters } from '#/parameters/getAllBoards';
+import { type CreateBoard as CreateBoardParameters } from '#/parameters/createBoard';
+import { type GetBoardByFilterId as GetBoardByFilterIdParameters } from '#/parameters/getBoardByFilterId';
+import { type GetBoard as GetBoardParameters } from '#/parameters/getBoard';
+import { type DeleteBoard } from '#/parameters/deleteBoard';
+import { type GetIssuesForBacklog } from '#/parameters/getIssuesForBacklog';
+import { type GetConfiguration as GetConfigurationParameters } from '#/parameters/getConfiguration';
+import { type GetEpics as GetEpicsParameters } from '#/parameters/getEpics';
+import { type GetIssuesWithoutEpicForBoard } from '#/parameters/getIssuesWithoutEpicForBoard';
+import { type GetBoardIssuesForEpic } from '#/parameters/getBoardIssuesForEpic';
+import { type GetFeaturesForBoard as GetFeaturesForBoardParameters } from '#/parameters/getFeaturesForBoard';
+import { type ToggleFeatures as ToggleFeaturesParameters } from '#/parameters/toggleFeatures';
+import { type GetIssuesForBoard } from '#/parameters/getIssuesForBoard';
+import { type MoveIssuesToBoard } from '#/parameters/moveIssuesToBoard';
+import { type GetProjects as GetProjectsParameters } from '#/parameters/getProjects';
+import { type GetProjectsFull as GetProjectsFullParameters } from '#/parameters/getProjectsFull';
+import { type GetBoardPropertyKeys } from '#/parameters/getBoardPropertyKeys';
+import { type GetBoardProperty } from '#/parameters/getBoardProperty';
+import { type SetBoardProperty } from '#/parameters/setBoardProperty';
+import { type DeleteBoardProperty } from '#/parameters/deleteBoardProperty';
+import { type GetAllQuickFilters as GetAllQuickFiltersParameters } from '#/parameters/getAllQuickFilters';
+import { type GetQuickFilter as GetQuickFilterParameters } from '#/parameters/getQuickFilter';
+import { type GetReportsForBoard as GetReportsForBoardParameters } from '#/parameters/getReportsForBoard';
+import { type GetAllSprints as GetAllSprintsParameters } from '#/parameters/getAllSprints';
+import { type GetBoardIssuesForSprint } from '#/parameters/getBoardIssuesForSprint';
+import { type GetAllVersions as GetAllVersionsParameters } from '#/parameters/getAllVersions';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Returns all boards. This only includes boards that the user has permission to view.
+ *
+ * **Deprecation notice:** The required OAuth 2.0 scopes will be updated on February 15, 2024.
+ *
+ * - `read:board-scope:jira-software`, `read:project:jira`
+ */
+export async function getAllBoards(client: Client, parameters?: GetAllBoardsParameters): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/board',
+ method: 'GET',
+ searchParams: {
+ startAt: parameters?.startAt,
+ maxResults: parameters?.maxResults,
+ type: parameters?.type,
+ name: parameters?.name,
+ projectKeyOrId: parameters?.projectKeyOrId,
+ accountIdLocation: parameters?.accountIdLocation,
+ projectLocation: parameters?.projectLocation,
+ includePrivate: parameters?.includePrivate,
+ negateLocationFiltering: parameters?.negateLocationFiltering,
+ orderBy: parameters?.orderBy,
+ expand: parameters?.expand,
+ projectTypeLocation: parameters?.projectTypeLocation,
+ filterId: parameters?.filterId,
+ },
+ schema: GetAllBoardsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Creates a new board. Board name, type and filter ID is required.
+ *
+ * - `name` - Must be less than 255 characters.
+ * - `type` - Valid values: scrum, kanban
+ * - `filterId` - ID of a filter that the user has permissions to view. Note, if the user does not have the 'Create shared
+ * objects' permission and tries to create a shared board, a private board will be created instead (remember that
+ * board sharing depends on the filter sharing).
+ * - `location` - The container that the board will be located in. `location` must include the `type` property (Valid
+ * values: project, user). If choosing 'project', then a project must be specified by a `projectKeyOrId` property in
+ * `location`. If choosing 'user', the current user is chosen by default. The `projectKeyOrId` property should not be
+ * provided.
+ *
+ * Note:
+ *
+ * - If you want to create a new project with an associated board, use the [Jira platform REST
+ * API](https://docs.atlassian.com/jira/REST/latest). For more information, see the [Create
+ * project](https://developer.atlassian.com/cloud/jira/software/rest/intro#api-rest-api-3-project-post) method. The
+ * `projectTypeKey` for software boards must be 'software' and the `projectTemplateKey` must be either
+ * `com.pyxis.greenhopper.jira:gh-kanban-template` or `com.pyxis.greenhopper.jira:gh-scrum-template`.
+ * - You can create a filter using the [Jira REST API](https://docs.atlassian.com/jira/REST/latest). For more information,
+ * see the [Create filter](https://developer.atlassian.com/cloud/jira/software/rest/intro#api-rest-api-3-filter-post)
+ * method.
+ * - If you do not ORDER BY the Rank field for the filter of your board, you will not be able to reorder issues on the
+ * board.
+ */
+export async function createBoard(client: Client, parameters: CreateBoardParameters): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/board',
+ method: 'POST',
+ body: {
+ filterId: parameters.filterId,
+ location: parameters.location,
+ name: parameters.name,
+ type: parameters.type,
+ },
+ schema: CreateBoardSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns any boards which use the provided filter id. This method can be executed by users without a valid software
+ * license in order to find which boards are using a particular filter.
+ */
+export async function getBoardByFilterId(
+ client: Client,
+ parameters: GetBoardByFilterIdParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/filter/${parameters.filterId}`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ },
+ schema: GetBoardByFilterIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the board for the given board ID. This board will only be returned if the user has permission to view it.
+ * Admins without the view permission will see the board as a private one, so will see only a subset of the board's data
+ * (board location for instance).
+ */
+export async function getBoard(client: Client, parameters: GetBoardParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}`,
+ method: 'GET',
+ schema: GetBoardSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/** Deletes the board. Admin without the view permission can still remove the board. */
+export async function deleteBoard(client: Client, parameters: DeleteBoard): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all issues from the board's backlog, for the given board ID. This only includes issues that the user has
+ * permission to view. The backlog contains incomplete issues that are not assigned to any future or active sprint.
+ * Note, if the user does not have permission to view the board, no issues will be returned at all. Issues returned from
+ * this resource include Agile fields, like sprint, closedSprints, flagged, and epic. By default, the returned issues
+ * are ordered by rank.
+ */
+export async function getIssuesForBacklog(client: Client, parameters: GetIssuesForBacklog): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/backlog`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Get the board configuration. The response contains the following fields:
+ *
+ * - `id` - ID of the board.
+ * - `name` - Name of the board.
+ * - `filter` - Reference to the filter used by the given board.
+ * - `location` - Reference to the container that the board is located in. Includes the container type (Valid values:
+ * project, user).
+ * - `subQuery` (Kanban only) - JQL subquery used by the given board.
+ * - `columnConfig` - The column configuration lists the columns for the board, in the order defined in the column
+ * configuration. For each column, it shows the issue status mapping as well as the constraint type (Valid values:
+ * none, issueCount, issueCountExclSubs) for the min/max number of issues. Note, the last column with statuses mapped
+ * to it is treated as the "Done" column, which means that issues in that column will be marked as already completed.
+ * - `estimation` (Scrum only) - Contains information about type of estimation used for the board. Valid values: none,
+ * issueCount, field. If the estimation type is "field", the ID and display name of the field used for estimation is
+ * also returned. Note, estimates for an issue can be updated by a PUT /rest/api/3/issue/{issueIdOrKey} request,
+ * however the fields must be on the screen. "timeoriginalestimate" field will never be on the screen, so in order to
+ * update it "originalEstimate" in "timetracking" field should be updated.
+ * - `ranking` - Contains information about custom field used for ranking in the given board.
+ */
+export async function getConfiguration(
+ client: Client,
+ parameters: GetConfigurationParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/configuration`,
+ method: 'GET',
+ schema: GetConfigurationSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all epics from the board, for the given board ID. This only includes epics that the user has permission to
+ * view. Note, if the user does not have permission to view the board, no epics will be returned at all.
+ */
+export async function getEpics(client: Client, parameters: GetEpicsParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/epic`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ done: parameters.done,
+ },
+ schema: GetEpicsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all issues that do not belong to any epic on a board, for a given board ID. This only includes issues that
+ * the user has permission to view. Issues returned from this resource include Agile fields, like sprint, closedSprints,
+ * flagged, and epic. By default, the returned issues are ordered by rank.
+ */
+export async function getIssuesWithoutEpicForBoard(
+ client: Client,
+ parameters: GetIssuesWithoutEpicForBoard,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/epic/none/issue`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all issues that belong to an epic on the board, for the given epic ID and the board ID. This only includes
+ * issues that the user has permission to view. Issues returned from this resource include Agile fields, like sprint,
+ * closedSprints, flagged, and epic. By default, the returned issues are ordered by rank.
+ */
+
+export async function getBoardIssuesForEpic(client: Client, parameters: GetBoardIssuesForEpic): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/epic/${parameters.epicId}/issue`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+export async function getFeaturesForBoard(
+ client: Client,
+ parameters: GetFeaturesForBoardParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/features`,
+ method: 'GET',
+ schema: GetFeaturesForBoardSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+export async function toggleFeatures(client: Client, parameters: ToggleFeaturesParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/features`,
+ method: 'PUT',
+ body: parameters.body,
+ schema: ToggleFeaturesSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all issues from a board, for a given board ID. This only includes issues that the user has permission to
+ * view. An issue belongs to the board if its status is mapped to the board's column. Epic issues do not belongs to the
+ * scrum boards. Note, if the user does not have permission to view the board, no issues will be returned at all. Issues
+ * returned from this resource include Agile fields, like sprint, closedSprints, flagged, and epic. By default, the
+ * returned issues are ordered by rank.
+ */
+export async function getIssuesForBoard(client: Client, parameters: GetIssuesForBoard): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/issue`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Move issues from the backog to the board (if they are already in the backlog of that board).\
+ * This operation either moves an issue(s) onto a board from the backlog (by adding it to the issueList for the board)
+ * Or transitions the issue(s) to the first column for a kanban board with backlog. At most 50 issues may be moved at
+ * once.
+ */
+export async function moveIssuesToBoard(client: Client, parameters: MoveIssuesToBoard): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/issue`,
+ method: 'POST',
+ body: {
+ issues: parameters.issues,
+ rankAfterIssue: parameters.rankAfterIssue,
+ rankBeforeIssue: parameters.rankBeforeIssue,
+ rankCustomFieldId: parameters.rankCustomFieldId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all projects that are associated with the board, for the given board ID. If the user does not have permission
+ * to view the board, no projects will be returned at all. Returned projects are ordered by the name.
+ *
+ * A project is associated with a board if the board filter contains reference the project or there is an issue from the
+ * project that belongs to the board.
+ *
+ * The board filter contains reference the project only if JQL query guarantees that returned issues will be returned
+ * from the project set defined in JQL. For instance the query `project in (ABC, BCD) AND reporter = admin` have
+ * reference to ABC and BCD projects but query `project in (ABC, BCD) OR reporter = admin` doesn't have reference to any
+ * project.
+ *
+ * An issue belongs to the board if its status is mapped to the board's column. Epic issues do not belongs to the scrum
+ * boards.
+ */
+export async function getProjects(client: Client, parameters: GetProjectsParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/project`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ },
+ schema: GetProjectsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all projects that are statically associated with the board, for the given board ID. Returned projects are
+ * ordered by the name.
+ *
+ * A project is associated with a board if the board filter contains reference the project.
+ *
+ * The board filter contains reference the project only if JQL query guarantees that returned issues will be returned
+ * from the project set defined in JQL. For instance the query `project in (ABC, BCD) AND reporter = admin` have
+ * reference to ABC and BCD projects but query `project in (ABC, BCD) OR reporter = admin` doesn't have reference to any
+ * project.
+ */
+export async function getProjectsFull(client: Client, parameters: GetProjectsFullParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/project/full`,
+ method: 'GET',
+ schema: GetProjectsFullSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the keys of all properties for the board identified by the id. The user who retrieves the property keys is
+ * required to have permissions to view the board.
+ */
+export async function getBoardPropertyKeys(client: Client, parameters: GetBoardPropertyKeys): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/properties`,
+ method: 'GET',
+ schema: PropertyKeysSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the value of the property with a given key from the board identified by the provided id. The user who
+ * retrieves the property is required to have permissions to view the board.
+ */
+export async function getBoardProperty(client: Client, parameters: GetBoardProperty): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/properties/${parameters.propertyKey}`,
+ method: 'GET',
+ schema: EntityPropertySchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Sets the value of the specified board's property.
+ *
+ * You can use this resource to store a custom data against the board identified by the id. The user who stores the data
+ * is required to have permissions to modify the board.
+ */
+export async function setBoardProperty(client: Client, parameters: SetBoardProperty): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/properties/${parameters.propertyKey}`,
+ method: 'PUT',
+ body: parameters.propertyValue,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Removes the property from the board identified by the id. Ths user removing the property is required to have
+ * permissions to modify the board.
+ */
+export async function deleteBoardProperty(client: Client, parameters: DeleteBoardProperty): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/properties/${parameters.propertyKey}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
+
+/** Returns all quick filters from a board, for a given board ID. */
+export async function getAllQuickFilters(
+ client: Client,
+ parameters: GetAllQuickFiltersParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/quickfilter`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ },
+ schema: GetAllQuickFiltersSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the quick filter for a given quick filter ID. The quick filter will only be returned if the user can view the
+ * board that the quick filter belongs to.
+ */
+
+export async function getQuickFilter(client: Client, parameters: GetQuickFilterParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/quickfilter/${parameters.quickFilterId}`,
+ method: 'GET',
+ schema: GetQuickFilterSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+export async function getReportsForBoard(
+ client: Client,
+ parameters: GetReportsForBoardParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/reports`,
+ method: 'GET',
+ schema: GetReportsForBoardSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all sprints from a board, for a given board ID. This only includes sprints that the user has permission to
+ * view.
+ */
+export async function getAllSprints(client: Client, parameters: GetAllSprintsParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/sprint`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ state: parameters.state,
+ },
+ schema: GetAllSprintsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Get all issues you have access to that belong to the sprint from the board. Issue returned from this resource
+ * contains additional fields like: sprint, closedSprints, flagged and epic. Issues are returned ordered by rank. JQL
+ * order has higher priority than default rank.
+ */
+export async function getBoardIssuesForSprint(
+ client: Client,
+ parameters: GetBoardIssuesForSprint,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/sprint/${parameters.sprintId}/issue`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all versions from a board, for a given board ID. This only includes versions that the user has permission to
+ * view. Note, if the user does not have permission to view the board, no versions will be returned at all. Returned
+ * versions are ordered by the name of the project from which they belong and then by sequence defined by user.
+ */
+export async function getAllVersions(client: Client, parameters: GetAllVersionsParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/board/${parameters.boardId}/version`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ released: parameters.released,
+ },
+ schema: GetAllVersionsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/builds.ts b/packages/agile/src/api/builds.ts
new file mode 100644
index 0000000000..7d59158c67
--- /dev/null
+++ b/packages/agile/src/api/builds.ts
@@ -0,0 +1,93 @@
+import { SubmitBuildsSchema, type SubmitBuilds } from '#/models/submitBuilds';
+import { GetBuildByKeySchema, type GetBuildByKey } from '#/models/getBuildByKey';
+import { type SubmitBuilds as SubmitBuildsParameters } from '#/parameters/submitBuilds';
+import { type DeleteBuildsByProperty } from '#/parameters/deleteBuildsByProperty';
+import { type GetBuildByKey as GetBuildByKeyParameters } from '#/parameters/getBuildByKey';
+import { type DeleteBuildByKey } from '#/parameters/deleteBuildByKey';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Update / insert builds data.
+ *
+ * Builds are identified by the combination of `pipelineId` and `buildNumber`, and existing build data for the same
+ * build will be replaced if it exists and the `updateSequenceNumber` of the existing data is less than the incoming
+ * data.
+ *
+ * Submissions are performed asynchronously. Submitted data will eventually be available in Jira; most updates are
+ * available within a short period of time, but may take some time during peak load and/or maintenance times. The
+ * `getBuildByKey` operation can be used to confirm that data has been stored successfully (if needed).
+ *
+ * In the case of multiple builds being submitted in one request, each is validated individually prior to submission.
+ * Details of which build failed submission (if any) are available in the response object.
+ */
+export async function submitBuilds(client: Client, parameters: SubmitBuildsParameters): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/builds/0.1/bulk',
+ method: 'POST',
+ body: {
+ properties: parameters.properties,
+ builds: parameters.builds,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitBuildsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all builds data that match the given request.
+ *
+ * One or more query params must be supplied to specify Properties to delete by. Optional param `_updateSequenceNumber`
+ * is no longer supported. If more than one Property is provided, data will be deleted that matches ALL of the
+ * Properties (e.g. treated as an AND).
+ *
+ * See the documentation for the `submitBuilds` operation for more details.
+ *
+ * E.g. DELETE /bulkByProperties?accountId=account-123&repoId=repo-345
+ *
+ * Deletion is performed asynchronously. The `getBuildByKey` operation can be used to confirm that data has been deleted
+ * successfully (if needed).
+ */
+export async function deleteBuildsByProperty(client: Client, parameters: DeleteBuildsByProperty): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/builds/0.1/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ accountId: parameters.accountId,
+ repoId: parameters.repoId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored build data for the given `pipelineId` and `buildNumber` combination.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ */
+export async function getBuildByKey(client: Client, parameters: GetBuildByKeyParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/builds/0.1/pipelines/${parameters.pipelineId}/builds/${parameters.buildNumber}`,
+ method: 'GET',
+ schema: GetBuildByKeySchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the build data currently stored for the given `pipelineId` and `buildNumber` combination.
+ *
+ * Deletion is performed asynchronously. The `getBuildByKey` operation can be used to confirm that data has been deleted
+ * successfully (if needed).
+ */
+export async function deleteBuildByKey(client: Client, parameters: DeleteBuildByKey): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/builds/0.1/pipelines/${parameters.pipelineId}/builds/${parameters.buildNumber}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/deployments.ts b/packages/agile/src/api/deployments.ts
new file mode 100644
index 0000000000..e3d20868ee
--- /dev/null
+++ b/packages/agile/src/api/deployments.ts
@@ -0,0 +1,126 @@
+import { SubmitDeploymentsSchema, type SubmitDeployments } from '#/models/submitDeployments';
+import { GetDeploymentByKeySchema, type GetDeploymentByKey } from '#/models/getDeploymentByKey';
+import {
+ GetDeploymentGatingStatusByKeySchema,
+ type GetDeploymentGatingStatusByKey,
+} from '#/models/getDeploymentGatingStatusByKey';
+import { type SubmitDeployments as SubmitDeploymentsParameters } from '#/parameters/submitDeployments';
+import { type DeleteDeploymentsByProperty } from '#/parameters/deleteDeploymentsByProperty';
+import { type GetDeploymentByKey as GetDeploymentByKeyParameters } from '#/parameters/getDeploymentByKey';
+import { type DeleteDeploymentByKey } from '#/parameters/deleteDeploymentByKey';
+import { type GetDeploymentGatingStatusByKey as GetDeploymentGatingStatusByKeyParameters } from '#/parameters/getDeploymentGatingStatusByKey';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Update / insert deployment data.
+ *
+ * Deployments are identified by the combination of `pipelineId`, `environmentId` and `deploymentSequenceNumber`, and
+ * existing deployment data for the same deployment will be replaced if it exists and the `updateSequenceNumber` of
+ * existing data is less than the incoming data.
+ *
+ * Submissions are processed asynchronously. Submitted data will eventually be available in Jira. Most updates are
+ * available within a short period of time, but may take some time during peak load and/or maintenance times. The
+ * `getDeploymentByKey` operation can be used to confirm that data has been stored successfully (if needed).
+ *
+ * In the case of multiple deployments being submitted in one request, each is validated individually prior to
+ * submission. Details of which deployments failed submission (if any) are available in the response object.
+ */
+export async function submitDeployments(
+ client: Client,
+ parameters: SubmitDeploymentsParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/deployments/0.1/bulk',
+ method: 'POST',
+ body: {
+ properties: parameters.properties,
+ deployments: parameters.deployments,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitDeploymentsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all deployments that match the given request.
+ *
+ * One or more query params must be supplied to specify the Properties to delete by. Optional param
+ * `_updateSequenceNumber` is no longer supported. If more than one Property is provided, data will be deleted that
+ * matches ALL of the Properties (i.e. treated as AND). See the documentation for the `submitDeployments` operation for
+ * more details.
+ *
+ * Example operation: DELETE /bulkByProperties?accountId=account-123&createdBy=user-456
+ *
+ * Deletion is performed asynchronously. The `getDeploymentByKey` operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ */
+export async function deleteDeploymentsByProperty(
+ client: Client,
+ parameters: DeleteDeploymentsByProperty,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/deployments/0.1/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ accountId: parameters.accountId,
+ createdBy: parameters.createdBy,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored deployment data for the given `pipelineId`, `environmentId` and
+ * `deploymentSequenceNumber` combination.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ */
+export async function getDeploymentByKey(
+ client: Client,
+ parameters: GetDeploymentByKeyParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/deployments/0.1/pipelines/${parameters.pipelineId}/environments/${parameters.environmentId}/deployments/${parameters.deploymentSequenceNumber}`,
+ method: 'GET',
+ schema: GetDeploymentByKeySchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the currently stored deployment data for the given `pipelineId`, `environmentId` and
+ * `deploymentSequenceNumber` combination.
+ *
+ * Deletion is performed asynchronously. The `getDeploymentByKey` operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ */
+export async function deleteDeploymentByKey(client: Client, parameters: DeleteDeploymentByKey): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/deployments/0.1/pipelines/${parameters.pipelineId}/environments/${parameters.environmentId}/deployments/${parameters.deploymentSequenceNumber}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the Deployment gating status for the given `pipelineId + environmentId + deploymentSequenceNumber`
+ * combination. Only apps that define the `jiraDeploymentInfoProvider` module can access this resource. This resource
+ * requires the 'READ' scope.
+ */
+export async function getDeploymentGatingStatusByKey(
+ client: Client,
+ parameters: GetDeploymentGatingStatusByKeyParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/deployments/0.1/pipelines/${parameters.pipelineId}/environments/${parameters.environmentId}/deployments/${parameters.deploymentSequenceNumber}/gating-status`,
+ method: 'GET',
+ schema: GetDeploymentGatingStatusByKeySchema,
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/developmentInformation.ts b/packages/agile/src/api/developmentInformation.ts
new file mode 100644
index 0000000000..ed59df9e28
--- /dev/null
+++ b/packages/agile/src/api/developmentInformation.ts
@@ -0,0 +1,126 @@
+import {
+ StoreDevelopmentInformationSchema,
+ type StoreDevelopmentInformation,
+} from '#/models/storeDevelopmentInformation';
+import { GetRepositorySchema, type GetRepository } from '#/models/getRepository';
+import { ExistsByPropertiesSchema, type ExistsByProperties } from '#/models/existsByProperties';
+import { type StoreDevelopmentInformation as StoreDevelopmentInformationParameters } from '#/parameters/storeDevelopmentInformation';
+import { type GetRepository as GetRepositoryParameters } from '#/parameters/getRepository';
+import { type DeleteRepository } from '#/parameters/deleteRepository';
+import { type DeleteByProperties } from '#/parameters/deleteByProperties';
+import { type ExistsByProperties as ExistsByPropertiesParameters } from '#/parameters/existsByProperties';
+import { type DeleteEntity } from '#/parameters/deleteEntity';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Stores development information provided in the request to make it available when viewing issues in Jira. Existing
+ * repository and entity data for the same ID will be replaced if the updateSequenceId of existing data is less than the
+ * incoming data. Submissions are performed asynchronously. Submitted data will eventually be available in Jira; most
+ * updates are available within a short period of time, but may take some time during peak load and/or maintenance
+ * times.
+ */
+export async function storeDevelopmentInformation(
+ client: Client,
+ parameters: StoreDevelopmentInformationParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/devinfo/0.10/bulk',
+ method: 'POST',
+ body: {
+ repositories: parameters.repositories,
+ preventTransitions: parameters.preventTransitions,
+ operationType: parameters.operationType,
+ properties: parameters.properties,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: StoreDevelopmentInformationSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * For the specified repository ID, retrieves the repository and the most recent 400 development information entities.
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ */
+export async function getRepository(client: Client, parameters: GetRepositoryParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/devinfo/0.10/repository/${parameters.repositoryId}`,
+ method: 'GET',
+ schema: GetRepositorySchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Deletes the repository data stored by the given ID and all related development information entities. Deletion is
+ * performed asynchronously.
+ */
+export async function deleteRepository(client: Client, parameters: DeleteRepository): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/devinfo/0.10/repository/${parameters.repositoryId}`,
+ method: 'DELETE',
+ searchParams: {
+ _updateSequenceId: parameters.updateSequenceId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Deletes development information entities which have all the provided properties. Repositories which have properties
+ * that match ALL of the properties (i.e. treated as an AND), and all their related development information (such as
+ * commits, branches and pull requests), will be deleted. For example if request is `DELETE
+ * bulk?accountId=123&projectId=ABC` entities which have properties `accountId=123` and `projectId=ABC` will be deleted.
+ * Optional param `_updateSequenceId` is no longer supported. Deletion is performed asynchronously: specified entities
+ * will eventually be removed from Jira.
+ */
+export async function deleteByProperties(client: Client, parameters: DeleteByProperties): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/devinfo/0.10/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ _updateSequenceId: parameters.updateSequenceId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Checks if repositories which have all the provided properties exists. For example, if request is `GET
+ * existsByProperties?accountId=123&projectId=ABC` then result will be positive only if there is at least one repository
+ * with both properties `accountId=123` and `projectId=ABC`. Special property `_updateSequenceId` can be used to filter
+ * all entities with updateSequenceId less or equal than the value specified. In addition to the optional
+ * `_updateSequenceId`, one or more query params must be supplied to specify properties to search by.
+ */
+export async function existsByProperties(
+ client: Client,
+ parameters?: ExistsByPropertiesParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/devinfo/0.10/existsByProperties',
+ method: 'GET',
+ searchParams: {
+ _updateSequenceId: parameters?.updateSequenceId,
+ },
+ schema: ExistsByPropertiesSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/** Deletes particular development information entity. Deletion is performed asynchronously. */
+export async function deleteEntity(client: Client, parameters: DeleteEntity): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/devinfo/0.10/repository/${parameters.repositoryId}/${parameters.entityType}/${parameters.entityId}`,
+ method: 'DELETE',
+ searchParams: {
+ _updateSequenceId: parameters.updateSequenceId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/devopsComponents.ts b/packages/agile/src/api/devopsComponents.ts
new file mode 100644
index 0000000000..c31c994ee1
--- /dev/null
+++ b/packages/agile/src/api/devopsComponents.ts
@@ -0,0 +1,113 @@
+import { SubmitComponentsSchema, type SubmitComponents } from '#/models/submitComponents';
+import { GetComponentByIdSchema, type GetComponentById } from '#/models/getComponentById';
+import { type SubmitComponents as SubmitComponentsParameters } from '#/parameters/submitComponents';
+import { type DeleteComponentsByProperty } from '#/parameters/deleteComponentsByProperty';
+import { type GetComponentById as GetComponentByIdParameters } from '#/parameters/getComponentById';
+import { type DeleteComponentById } from '#/parameters/deleteComponentById';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Update / insert DevOps Component data.
+ *
+ * Components are identified by their ID, and existing Component data for the same ID will be replaced if it exists and
+ * the updateSequenceNumber of existing data is less than the incoming data.
+ *
+ * Submissions are performed asynchronously. Submitted data will eventually be available in Jira; most updates are
+ * available within a short period of time, but may take some time during peak load and/or maintenance times. The
+ * getComponentById operation can be used to confirm that data has been stored successfully (if needed).
+ *
+ * In the case of multiple Components being submitted in one request, each is validated individually prior to
+ * submission. Details of which Components failed submission (if any) are available in the response object.
+ *
+ * A maximum of 1000 components can be submitted in one request.
+ *
+ * Only Connect apps that define the `jiraDevOpsComponentProvider` module can access this resource. This resource
+ * requires the 'WRITE' scope for Connect apps.
+ */
+export async function submitComponents(
+ client: Client,
+ parameters: SubmitComponentsParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/devopscomponents/1.0/bulk',
+ method: 'POST',
+ body: {
+ properties: parameters.properties,
+ devopsComponents: parameters.devopsComponents,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitComponentsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all Components that match the given request.
+ *
+ * One or more query params must be supplied to specify Properties to delete by. If more than one Property is provided,
+ * data will be deleted that matches ALL of the Properties (e.g. treated as an AND). See the documentation for the
+ * submitComponents operation for more details.
+ *
+ * E.g. DELETE /bulkByProperties?accountId=account-123&createdBy=user-456
+ *
+ * Deletion is performed asynchronously. The getComponentById operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraDevOpsComponentProvider` module can access this resource. This resource
+ * requires the 'DELETE' scope for Connect apps.
+ */
+export async function deleteComponentsByProperty(
+ client: Client,
+ parameters: DeleteComponentsByProperty,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/devopscomponents/1.0/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ accountId: parameters.accountId,
+ createdBy: parameters.createdBy,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored Component data for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * Only Connect apps that define the `jiraDevOpsComponentProvider` module can access this resource. This resource
+ * requires the 'READ' scope for Connect apps.
+ */
+export async function getComponentById(
+ client: Client,
+ parameters: GetComponentByIdParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/devopscomponents/1.0/devopscomponents/${parameters.componentId}`,
+ method: 'GET',
+ schema: GetComponentByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the Component data currently stored for the given ID.
+ *
+ * Deletion is performed asynchronously. The getComponentById operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraDevOpsComponentProvider` module can access this resource. This resource
+ * requires the 'DELETE' scope for Connect apps.
+ */
+export async function deleteComponentById(client: Client, parameters: DeleteComponentById): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/devopscomponents/1.0/devopscomponents/${parameters.componentId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/epic.ts b/packages/agile/src/api/epic.ts
new file mode 100644
index 0000000000..415a32120b
--- /dev/null
+++ b/packages/agile/src/api/epic.ts
@@ -0,0 +1,155 @@
+import { SearchResultsSchema, type SearchResults } from '#/models/searchResults';
+import { EpicSchema, type Epic } from '#/models/epic';
+import { type GetIssuesWithoutEpic } from '#/parameters/getIssuesWithoutEpic';
+import { type RemoveIssuesFromEpic } from '#/parameters/removeIssuesFromEpic';
+import { type GetEpic } from '#/parameters/getEpic';
+import { type PartiallyUpdateEpic } from '#/parameters/partiallyUpdateEpic';
+import { type GetIssuesForEpic } from '#/parameters/getIssuesForEpic';
+import { type MoveIssuesToEpic } from '#/parameters/moveIssuesToEpic';
+import { type RankEpics } from '#/parameters/rankEpics';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Returns all issues that do not belong to any epic. This only includes issues that the user has permission to view.
+ * Issues returned from this resource include Agile fields, like sprint, closedSprints, flagged, and epic. By default,
+ * the returned issues are ordered by rank. **Note:** If you are querying a next-gen project, do not use this operation.
+ * Instead, search for issues that don't belong to an epic by using the [Search for issues using
+ * JQL](https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-get) operation in the Jira
+ * platform REST API. Build your JQL query using the `parent is empty` clause. For more information on the `parent` JQL
+ * field, see [Advanced searching](https://confluence.atlassian.com/x/dAiiLQ#Advancedsearching-fieldsreference-Parent).
+ */
+export async function getIssuesWithoutEpic(client: Client, parameters?: GetIssuesWithoutEpic): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/epic/none/issue',
+ method: 'GET',
+ searchParams: {
+ startAt: parameters?.startAt,
+ maxResults: parameters?.maxResults,
+ jql: parameters?.jql,
+ validateQuery: parameters?.validateQuery,
+ fields: parameters?.fields,
+ expand: parameters?.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Removes issues from epics. The user needs to have the edit issue permission for all issue they want to remove from
+ * epics. The maximum number of issues that can be moved in one operation is 50. **Note:** This operation does not work
+ * for epics in next-gen projects. Instead, update the issue using `\{ fields: \{ parent: \{\} \} \}`
+ */
+export async function removeIssuesFromEpic(client: Client, parameters: RemoveIssuesFromEpic): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/epic/none/issue',
+ method: 'POST',
+ body: {
+ issues: parameters.issues,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the epic for a given epic ID. This epic will only be returned if the user has permission to view it.
+ * **Note:** This operation does not work for epics in next-gen projects.
+ */
+export async function getEpic(client: Client, parameters: GetEpic): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/epic/${parameters.epicIdOrKey}`,
+ method: 'GET',
+ schema: EpicSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Performs a partial update of the epic. A partial update means that fields not present in the request JSON will not be
+ * updated. Valid values for color are `color_1` to `color_9`. **Note:** This operation does not work for epics in
+ * next-gen projects.
+ */
+export async function partiallyUpdateEpic(client: Client, parameters: PartiallyUpdateEpic): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/epic/${parameters.epicIdOrKey}`,
+ method: 'POST',
+ body: {
+ color: parameters.color,
+ done: parameters.done,
+ name: parameters.name,
+ summary: parameters.summary,
+ },
+ schema: EpicSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all issues that belong to the epic, for the given epic ID. This only includes issues that the user has
+ * permission to view. Issues returned from this resource include Agile fields, like sprint, closedSprints, flagged, and
+ * epic. By default, the returned issues are ordered by rank. **Note:** If you are querying a next-gen project, do not
+ * use this operation. Instead, search for issues that belong to an epic by using the [Search for issues using
+ * JQL](https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-get) operation in the Jira
+ * platform REST API. Build your JQL query using the `parent` clause. For more information on the `parent` JQL field,
+ * see [Advanced searching](https://confluence.atlassian.com/x/dAiiLQ#Advancedsearching-fieldsreference-Parent).
+ */
+export async function getIssuesForEpic(client: Client, parameters: GetIssuesForEpic): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/epic/${parameters.epicIdOrKey}/issue`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Moves issues to an epic, for a given epic id. Issues can be only in a single epic at the same time. That means that
+ * already assigned issues to an epic, will not be assigned to the previous epic anymore. The user needs to have the
+ * edit issue permission for all issue they want to move and to the epic. The maximum number of issues that can be moved
+ * in one operation is 50. **Note:** This operation does not work for epics in next-gen projects.
+ */
+export async function moveIssuesToEpic(client: Client, parameters: MoveIssuesToEpic): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/epic/${parameters.epicIdOrKey}/issue`,
+ method: 'POST',
+ body: {
+ issues: parameters.issues,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Moves (ranks) an epic before or after a given epic.
+ *
+ * If rankCustomFieldId is not defined, the default rank field will be used.
+ *
+ * **Note:** This operation does not work for epics in next-gen projects.
+ */
+export async function rankEpics(client: Client, parameters: RankEpics): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/epic/${parameters.epicIdOrKey}/rank`,
+ method: 'PUT',
+ body: {
+ rankAfterEpic: parameters.rankAfterEpic,
+ rankBeforeEpic: parameters.rankBeforeEpic,
+ rankCustomFieldId: parameters.rankCustomFieldId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/featureFlags.ts b/packages/agile/src/api/featureFlags.ts
new file mode 100644
index 0000000000..fbb079a3b0
--- /dev/null
+++ b/packages/agile/src/api/featureFlags.ts
@@ -0,0 +1,99 @@
+import { SubmitFeatureFlagsSchema, type SubmitFeatureFlags } from '#/models/submitFeatureFlags';
+import { GetFeatureFlagByIdSchema, type GetFeatureFlagById } from '#/models/getFeatureFlagById';
+import { type SubmitFeatureFlags as SubmitFeatureFlagsParameters } from '#/parameters/submitFeatureFlags';
+import { type DeleteFeatureFlagsByProperty } from '#/parameters/deleteFeatureFlagsByProperty';
+import { type GetFeatureFlagById as GetFeatureFlagByIdParameters } from '#/parameters/getFeatureFlagById';
+import { type DeleteFeatureFlagById } from '#/parameters/deleteFeatureFlagById';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Update / insert Feature Flag data.
+ *
+ * Feature Flags are identified by their ID, and existing Feature Flag data for the same ID will be replaced if it
+ * exists and the updateSequenceId of existing data is less than the incoming data.
+ *
+ * Submissions are performed asynchronously. Submitted data will eventually be available in Jira; most updates are
+ * available within a short period of time, but may take some time during peak load and/or maintenance times. The
+ * getFeatureFlagById operation can be used to confirm that data has been stored successfully (if needed).
+ *
+ * In the case of multiple Feature Flags being submitted in one request, each is validated individually prior to
+ * submission. Details of which Feature Flags failed submission (if any) are available in the response object.
+ */
+export async function submitFeatureFlags(
+ client: Client,
+ parameters: SubmitFeatureFlagsParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/featureflags/0.1/bulk',
+ method: 'POST',
+ body: {
+ properties: parameters.properties,
+ flags: parameters.flags,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitFeatureFlagsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all Feature Flags that match the given request.
+ *
+ * One or more query params must be supplied to specify Properties to delete by. Optional param `_updateSequenceId` is
+ * no longer supported. If more than one Property is provided, data will be deleted that matches ALL of the Properties
+ * (e.g. treated as an AND). See the documentation for the submitFeatureFlags operation for more details.
+ *
+ * E.g. DELETE /bulkByProperties?accountId=account-123&createdBy=user-456
+ *
+ * Deletion is performed asynchronously. The getFeatureFlagById operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ */
+export async function deleteFeatureFlagsByProperty(
+ client: Client,
+ parameters: DeleteFeatureFlagsByProperty,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/featureflags/0.1/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ accountId: parameters.accountId,
+ createdBy: parameters.createdBy,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored Feature Flag data for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ */
+export async function getFeatureFlagById(
+ client: Client,
+ parameters: GetFeatureFlagByIdParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/featureflags/0.1/flag/${parameters.featureFlagId}`,
+ method: 'GET',
+ schema: GetFeatureFlagByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the Feature Flag data currently stored for the given ID.
+ *
+ * Deletion is performed asynchronously. The getFeatureFlagById operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ */
+export async function deleteFeatureFlagById(client: Client, parameters: DeleteFeatureFlagById): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/featureflags/0.1/flag/${parameters.featureFlagId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/index.ts b/packages/agile/src/api/index.ts
new file mode 100644
index 0000000000..2325262399
--- /dev/null
+++ b/packages/agile/src/api/index.ts
@@ -0,0 +1,25 @@
+export * from './backlog';
+
+export * from './board';
+
+export * from './builds';
+
+export * from './deployments';
+
+export * from './developmentInformation';
+
+export * from './devopsComponents';
+
+export * from './epic';
+
+export * from './featureFlags';
+
+export * from './issue';
+
+export * from './operations';
+
+export * from './remoteLinks';
+
+export * from './securityInformation';
+
+export * from './sprint';
diff --git a/packages/agile/src/api/issue.ts b/packages/agile/src/api/issue.ts
new file mode 100644
index 0000000000..66a8f73441
--- /dev/null
+++ b/packages/agile/src/api/issue.ts
@@ -0,0 +1,112 @@
+import { IssueSchema, type Issue } from '#/models/issue';
+import { GetIssueEstimationForBoardSchema, type GetIssueEstimationForBoard } from '#/models/getIssueEstimationForBoard';
+import { EstimateIssueForBoardSchema, type EstimateIssueForBoard } from '#/models/estimateIssueForBoard';
+import { type RankIssues } from '#/parameters/rankIssues';
+import { type GetIssue } from '#/parameters/getIssue';
+import { type GetIssueEstimationForBoard as GetIssueEstimationForBoardParameters } from '#/parameters/getIssueEstimationForBoard';
+import { type EstimateIssueForBoard as EstimateIssueForBoardParameters } from '#/parameters/estimateIssueForBoard';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Moves (ranks) issues before or after a given issue. At most 50 issues may be ranked at once.
+ *
+ * This operation may fail for some issues, although this will be rare. In that case the 207 status code is returned for
+ * the whole response and detailed information regarding each issue is available in the response body.
+ *
+ * If rankCustomFieldId is not defined, the default rank field will be used.
+ */
+export async function rankIssues(client: Client, parameters: RankIssues): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/issue/rank',
+ method: 'PUT',
+ body: {
+ issues: parameters.issues,
+ rankAfterIssue: parameters.rankAfterIssue,
+ rankBeforeIssue: parameters.rankBeforeIssue,
+ rankCustomFieldId: parameters.rankCustomFieldId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns a single issue, for a given issue ID or issue key. Issues returned from this resource include Agile fields,
+ * like sprint, closedSprints, flagged, and epic.
+ */
+export async function getIssue(client: Client, parameters: GetIssue): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/issue/${parameters.issueIdOrKey}`,
+ method: 'GET',
+ searchParams: {
+ fields: parameters.fields,
+ expand: parameters.expand,
+ updateHistory: parameters.updateHistory,
+ },
+ schema: IssueSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the estimation of the issue and a fieldId of the field that is used for it. `boardId` param is required. This
+ * param determines which field will be updated on a issue.
+ *
+ * Original time internally stores and returns the estimation as a number of seconds.
+ *
+ * The field used for estimation on the given board can be obtained from [board configuration
+ * resource](https://developer.atlassian.com/cloud/jira/software/rest/intro#agile/1.0/board-getConfiguration). More
+ * information about the field are returned by [edit meta
+ * resource](https://developer.atlassian.com/cloud/jira/software/rest/intro#api-rest-api-3-issue-getEditIssueMeta) or
+ * [field resource](https://developer.atlassian.com/cloud/jira/software/rest/intro#api-rest-api-3-field-get).
+ */
+export async function getIssueEstimationForBoard(
+ client: Client,
+ parameters: GetIssueEstimationForBoardParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/issue/${parameters.issueIdOrKey}/estimation`,
+ method: 'GET',
+ searchParams: {
+ boardId: parameters.boardId,
+ },
+ schema: GetIssueEstimationForBoardSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Updates the estimation of the issue. boardId param is required. This param determines which field will be updated on
+ * a issue.
+ *
+ * Note that this resource changes the estimation field of the issue regardless of appearance the field on the screen.
+ *
+ * Original time tracking estimation field accepts estimation in formats like "1w", "2d", "3h", "20m" or number which
+ * represent number of minutes. However, internally the field stores and returns the estimation as a number of seconds.
+ *
+ * The field used for estimation on the given board can be obtained from [board configuration
+ * resource](https://developer.atlassian.com/cloud/jira/software/rest/intro#agile/1.0/board-getConfiguration). More
+ * information about the field are returned by [edit meta
+ * resource](https://developer.atlassian.com/cloud/jira/software/rest/intro#api-rest-api-3-issue-issueIdOrKey-editmeta-get)
+ * or [field resource](https://developer.atlassian.com/cloud/jira/software/rest/intro#api-rest-api-3-field-get).
+ */
+export async function estimateIssueForBoard(
+ client: Client,
+ parameters: EstimateIssueForBoardParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/issue/${parameters.issueIdOrKey}/estimation`,
+ method: 'PUT',
+ searchParams: {
+ boardId: parameters.boardId,
+ },
+ body: {
+ value: parameters.value,
+ },
+ schema: EstimateIssueForBoardSchema,
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/operations.ts b/packages/agile/src/api/operations.ts
new file mode 100644
index 0000000000..cd4bb6d986
--- /dev/null
+++ b/packages/agile/src/api/operations.ts
@@ -0,0 +1,216 @@
+import { SubmitOperationsWorkspacesSchema, type SubmitOperationsWorkspaces } from '#/models/submitOperationsWorkspaces';
+import { GetWorkspacesSchema, type GetWorkspaces } from '#/models/getWorkspaces';
+import { SubmitEntitySchema, type SubmitEntity } from '#/models/submitEntity';
+import { GetIncidentByIdSchema, type GetIncidentById } from '#/models/getIncidentById';
+import { GetReviewByIdSchema, type GetReviewById } from '#/models/getReviewById';
+import { type SubmitOperationsWorkspaces as SubmitOperationsWorkspacesParameters } from '#/parameters/submitOperationsWorkspaces';
+import { type DeleteWorkspaces } from '#/parameters/deleteWorkspaces';
+import { type GetWorkspaces as GetWorkspacesParameters } from '#/parameters/getWorkspaces';
+import { type SubmitEntity as SubmitEntityParameters } from '#/parameters/submitEntity';
+import { type DeleteEntityByProperty } from '#/parameters/deleteEntityByProperty';
+import { type GetIncidentById as GetIncidentByIdParameters } from '#/parameters/getIncidentById';
+import { type DeleteIncidentById } from '#/parameters/deleteIncidentById';
+import { type GetReviewById as GetReviewByIdParameters } from '#/parameters/getReviewById';
+import { type DeleteReviewById } from '#/parameters/deleteReviewById';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Insert Operations Workspace IDs to establish a relationship between them and the Jira site the app is installed in.
+ * If a relationship between the Workspace ID and Jira already exists then the workspace ID will be ignored and Jira
+ * will process the rest of the entries.
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'WRITE' scope for Connect apps.
+ */
+export async function submitOperationsWorkspaces(
+ client: Client,
+ parameters: SubmitOperationsWorkspacesParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/operations/1.0/linkedWorkspaces/bulk',
+ method: 'POST',
+ body: {
+ workspaceIds: parameters.workspaceIds,
+ },
+ schema: SubmitOperationsWorkspacesSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all Operations Workspaces that match the given request.
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'DELETE' scope for Connect apps.
+ *
+ * E.g. DELETE /bulk?workspaceIds=111-222-333,444-555-666
+ */
+export async function deleteWorkspaces(client: Client, parameters: DeleteWorkspaces): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/operations/1.0/linkedWorkspaces/bulk',
+ method: 'DELETE',
+ searchParams: {
+ workspaceIds: parameters.workspaceIds,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the either all Operations Workspace IDs associated with the Jira site or a specific Operations Workspace ID
+ * for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * E.g. GET /workspace?workspaceId=111-222-333
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'READ' scope for Connect apps.
+ */
+export async function getWorkspaces(client: Client, parameters?: GetWorkspacesParameters): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/operations/1.0/linkedWorkspaces',
+ method: 'GET',
+ searchParams: {
+ workspaceId: parameters?.workspaceId,
+ },
+ schema: GetWorkspacesSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Update / insert Incident or Review data.
+ *
+ * Incidents and reviews are identified by their ID, and existing Incident and Review data for the same ID will be
+ * replaced if it exists and the updateSequenceNumber of existing data is less than the incoming data.
+ *
+ * Submissions are performed asynchronously. Submitted data will eventually be available in Jira; most updates are
+ * available within a short period of time, but may take some time during peak load and/or maintenance times. The
+ * getIncidentById or getReviewById operation can be used to confirm that data has been stored successfully (if
+ * needed).
+ *
+ * In the case of multiple Incidents and Reviews being submitted in one request, each is validated individually prior to
+ * submission. Details of which entities failed submission (if any) are available in the response object.
+ *
+ * A maximum of 1000 incidents can be submitted in one request.
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'WRITE' scope for Connect apps.
+ */
+export async function submitEntity(client: Client, parameters: SubmitEntityParameters): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/operations/1.0/bulk',
+ method: 'POST',
+ body: {
+ properties: parameters.properties,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitEntitySchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all Entties that match the given request.
+ *
+ * One or more query params must be supplied to specify Properties to delete by. If more than one Property is provided,
+ * data will be deleted that matches ALL of the Properties (e.g. treated as an AND). See the documentation for the
+ * submitEntity operation for more details.
+ *
+ * E.g. DELETE /bulkByProperties?accountId=account-123&createdBy=user-456
+ *
+ * Deletion is performed asynchronously. The getIncidentById operation can be used to confirm that data has been deleted
+ * successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'DELETE' scope for Connect apps.
+ */
+export async function deleteEntityByProperty(client: Client, parameters: DeleteEntityByProperty): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/operations/1.0/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ accountId: parameters.accountId,
+ createdBy: parameters.createdBy,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored Incident data for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'READ' scope for Connect apps.
+ */
+export async function getIncidentById(client: Client, parameters: GetIncidentByIdParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/operations/1.0/incidents/${parameters.incidentId}`,
+ method: 'GET',
+ schema: GetIncidentByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the Incident data currently stored for the given ID.
+ *
+ * Deletion is performed asynchronously. The getIncidentById operation can be used to confirm that data has been deleted
+ * successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'DELETE' scope for Connect apps.
+ */
+export async function deleteIncidentById(client: Client, parameters: DeleteIncidentById): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/operations/1.0/incidents/${parameters.incidentId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored Review data for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'READ' scope for Connect apps.
+ */
+export async function getReviewById(client: Client, parameters: GetReviewByIdParameters): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/operations/1.0/post-incident-reviews/${parameters.reviewId}`,
+ method: 'GET',
+ schema: GetReviewByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the Review data currently stored for the given ID.
+ *
+ * Deletion is performed asynchronously. The getReviewById operation can be used to confirm that data has been deleted
+ * successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraOperationsInfoProvider` module can access this resource. This resource
+ * requires the 'DELETE' scope for Connect apps.
+ */
+export async function deleteReviewById(client: Client, parameters: DeleteReviewById): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/operations/1.0/post-incident-reviews/${parameters.reviewId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/remoteLinks.ts b/packages/agile/src/api/remoteLinks.ts
new file mode 100644
index 0000000000..2d181cd98d
--- /dev/null
+++ b/packages/agile/src/api/remoteLinks.ts
@@ -0,0 +1,100 @@
+import { SubmitRemoteLinksSchema, type SubmitRemoteLinks } from '#/models/submitRemoteLinks';
+import { GetRemoteLinkByIdSchema, type GetRemoteLinkById } from '#/models/getRemoteLinkById';
+import { type SubmitRemoteLinks as SubmitRemoteLinksParameters } from '#/parameters/submitRemoteLinks';
+import { type DeleteRemoteLinksByProperty } from '#/parameters/deleteRemoteLinksByProperty';
+import { type GetRemoteLinkById as GetRemoteLinkByIdParameters } from '#/parameters/getRemoteLinkById';
+import { type DeleteRemoteLinkById } from '#/parameters/deleteRemoteLinkById';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Update / insert Remote Link data.
+ *
+ * Remote Links are identified by their ID, existing Remote Link data for the same ID will be replaced if it exists and
+ * the updateSequenceId of existing data is less than the incoming data.
+ *
+ * Submissions are performed asynchronously. Submitted data will eventually be available in Jira; most updates are
+ * available within a short period of time, but may take some time during peak load and/or maintenance times. The
+ * `getRemoteLinkById` operation can be used to confirm that data has been stored successfully (if needed).
+ *
+ * In the case of multiple Remote Links being submitted in one request, each is validated individually prior to
+ * submission. Details of which Remote LInk failed submission (if any) are available in the response object.
+ */
+export async function submitRemoteLinks(
+ client: Client,
+ parameters: SubmitRemoteLinksParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/remotelinks/1.0/bulk',
+ method: 'POST',
+ body: {
+ properties: parameters.properties,
+ remoteLinks: parameters.remoteLinks,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitRemoteLinksSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all Remote Links data that match the given request.
+ *
+ * One or more query params must be supplied to specify Properties to delete by. Optional param `_updateSequenceNumber`
+ * is no longer supported. If more than one Property is provided, data will be deleted that matches ALL of the
+ * Properties (e.g. treated as an AND).
+ *
+ * See the documentation for the `submitRemoteLinks` operation for more details.
+ *
+ * E.g. DELETE /bulkByProperties?accountId=account-123&repoId=repo-345
+ *
+ * Deletion is performed asynchronously. The `getRemoteLinkById` operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ */
+export async function deleteRemoteLinksByProperty(
+ client: Client,
+ parameters: DeleteRemoteLinksByProperty,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/remotelinks/1.0/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ params: parameters.params,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored Remote Link data for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ */
+export async function getRemoteLinkById(
+ client: Client,
+ parameters: GetRemoteLinkByIdParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/remotelinks/1.0/remotelink/${parameters.remoteLinkId}`,
+ method: 'GET',
+ schema: GetRemoteLinkByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the Remote Link data currently stored for the given ID.
+ *
+ * Deletion is performed asynchronously. The `getRemoteLinkById` operation can be used to confirm that data has been
+ * deleted successfully (if needed).
+ */
+export async function deleteRemoteLinkById(client: Client, parameters: DeleteRemoteLinkById): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/remotelinks/1.0/remotelink/${parameters.remoteLinkId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/securityInformation.ts b/packages/agile/src/api/securityInformation.ts
new file mode 100644
index 0000000000..175ee23bd1
--- /dev/null
+++ b/packages/agile/src/api/securityInformation.ts
@@ -0,0 +1,198 @@
+import { GetLinkedWorkspacesSchema, type GetLinkedWorkspaces } from '#/models/getLinkedWorkspaces';
+import { GetLinkedWorkspaceByIdSchema, type GetLinkedWorkspaceById } from '#/models/getLinkedWorkspaceById';
+import { SubmitVulnerabilitiesSchema, type SubmitVulnerabilities } from '#/models/submitVulnerabilities';
+import { GetVulnerabilityByIdSchema, type GetVulnerabilityById } from '#/models/getVulnerabilityById';
+import { type SubmitWorkspaces } from '#/parameters/submitWorkspaces';
+import { type DeleteLinkedWorkspaces } from '#/parameters/deleteLinkedWorkspaces';
+import { type GetLinkedWorkspaceById as GetLinkedWorkspaceByIdParameters } from '#/parameters/getLinkedWorkspaceById';
+import { type SubmitVulnerabilities as SubmitVulnerabilitiesParameters } from '#/parameters/submitVulnerabilities';
+import { type DeleteVulnerabilitiesByProperty } from '#/parameters/deleteVulnerabilitiesByProperty';
+import { type GetVulnerabilityById as GetVulnerabilityByIdParameters } from '#/parameters/getVulnerabilityById';
+import { type DeleteVulnerabilityById } from '#/parameters/deleteVulnerabilityById';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Insert Security Workspace IDs to establish a relationship between them and the Jira site the app is installed on. If
+ * a relationship between the workspace ID and Jira already exists then the workspace ID will be ignored and Jira will
+ * process the rest of the entries.
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'WRITE' scope for Connect apps.
+ */
+export async function submitWorkspaces(client: Client, parameters: SubmitWorkspaces): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/security/1.0/linkedWorkspaces/bulk',
+ method: 'POST',
+ body: {
+ workspaceIds: parameters.workspaceIds,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all linked Security Workspaces that match the given request.
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'DELETE' scope for Connect apps.
+ *
+ * E.g. DELETE /bulk?workspaceIds=111-222-333,444-555-666
+ */
+export async function deleteLinkedWorkspaces(client: Client, parameters: DeleteLinkedWorkspaces): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/security/1.0/linkedWorkspaces/bulk',
+ method: 'DELETE',
+ searchParams: {
+ workspaceIds: parameters.workspaceIds,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve all Security Workspaces linked with the Jira site.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'READ' scope for Connect apps.
+ */
+export async function getLinkedWorkspaces(client: Client): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/security/1.0/linkedWorkspaces',
+ method: 'GET',
+ schema: GetLinkedWorkspacesSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve a specific Security Workspace linked to the Jira site for the given workspace ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'READ' scope for Connect apps.
+ */
+export async function getLinkedWorkspaceById(
+ client: Client,
+ parameters: GetLinkedWorkspaceByIdParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/security/1.0/linkedWorkspaces/${parameters.workspaceId}`,
+ method: 'GET',
+ schema: GetLinkedWorkspaceByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Update / Insert Vulnerability data.
+ *
+ * Vulnerabilities are identified by their ID, any existing Vulnerability data with the same ID will be replaced if it
+ * exists and the updateSequenceNumber of the existing data is less than the incoming data.
+ *
+ * Submissions are performed asynchronously. Most updates are available within a short period of time but may take some
+ * time during peak load and/or maintenance times. The GET vulnerability endpoint can be used to confirm that data has
+ * been stored successfully (if needed).
+ *
+ * In the case of multiple Vulnerabilities being submitted in one request, each is validated individually prior to
+ * submission. Details of Vulnerabilities that failed submission (if any) are available in the response object.
+ *
+ * A maximum of 1000 vulnerabilities can be submitted in one request.
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'WRITE' scope for Connect apps.
+ */
+export async function submitVulnerabilities(
+ client: Client,
+ parameters: SubmitVulnerabilitiesParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/security/1.0/bulk',
+ method: 'POST',
+ body: {
+ operationType: parameters.operationType,
+ properties: parameters.properties,
+ vulnerabilities: parameters.vulnerabilities,
+ providerMetadata: parameters.providerMetadata,
+ },
+ schema: SubmitVulnerabilitiesSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Bulk delete all Vulnerabilities that match the given request.
+ *
+ * One or more query params must be supplied to specify Properties to delete by. If more than one Property is provided,
+ * data will be deleted that matches ALL of the Properties (e.g. treated as an AND). Read the POST bulk endpoint
+ * documentation for more details.
+ *
+ * E.g. DELETE /bulkByProperties?accountId=account-123&createdBy=user-456
+ *
+ * Deletion is performed asynchronously. The GET vulnerability endpoint can be used to confirm that data has been
+ * deleted successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'DELETE' scope for Connect apps.
+ */
+export async function deleteVulnerabilitiesByProperty(
+ client: Client,
+ parameters: DeleteVulnerabilitiesByProperty,
+): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/security/1.0/bulkByProperties',
+ method: 'DELETE',
+ searchParams: {
+ accountId: parameters.accountId,
+ createdBy: parameters.createdBy,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Retrieve the currently stored Vulnerability data for the given ID.
+ *
+ * The result will be what is currently stored, ignoring any pending updates or deletes.
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'READ' scope for Connect apps.
+ */
+export async function getVulnerabilityById(
+ client: Client,
+ parameters: GetVulnerabilityByIdParameters,
+): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/security/1.0/vulnerability/${parameters.vulnerabilityId}`,
+ method: 'GET',
+ schema: GetVulnerabilityByIdSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Delete the Vulnerability data currently stored for the given ID.
+ *
+ * Deletion is performed asynchronously. The GET vulnerability endpoint can be used to confirm that data has been
+ * deleted successfully (if needed).
+ *
+ * Only Connect apps that define the `jiraSecurityInfoProvider` module can access this resource. This resource requires
+ * the 'DELETE' scope for Connect apps.
+ */
+export async function deleteVulnerabilityById(client: Client, parameters: DeleteVulnerabilityById): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/security/1.0/vulnerability/${parameters.vulnerabilityId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/api/sprint.ts b/packages/agile/src/api/sprint.ts
new file mode 100644
index 0000000000..2823fb084a
--- /dev/null
+++ b/packages/agile/src/api/sprint.ts
@@ -0,0 +1,245 @@
+import { SprintSchema, type Sprint } from '#/models/sprint';
+import { SearchResultsSchema, type SearchResults } from '#/models/searchResults';
+import { PropertyKeysSchema, type PropertyKeys } from '#/models/propertyKeys';
+import { EntityPropertySchema, type EntityProperty } from '#/models/entityProperty';
+import { type CreateSprint } from '#/parameters/createSprint';
+import { type GetSprint } from '#/parameters/getSprint';
+import { type PartiallyUpdateSprint } from '#/parameters/partiallyUpdateSprint';
+import { type UpdateSprint } from '#/parameters/updateSprint';
+import { type DeleteSprint } from '#/parameters/deleteSprint';
+import { type GetIssuesForSprint } from '#/parameters/getIssuesForSprint';
+import { type MoveIssuesToSprintAndRank } from '#/parameters/moveIssuesToSprintAndRank';
+import { type GetPropertiesKeys } from '#/parameters/getPropertiesKeys';
+import { type GetProperty } from '#/parameters/getProperty';
+import { type SetProperty } from '#/parameters/setProperty';
+import { type DeleteProperty } from '#/parameters/deleteProperty';
+import { type SwapSprint } from '#/parameters/swapSprint';
+import { type Client, type SendRequestOptions } from '@jira.js/base';
+
+/**
+ * Creates a future sprint. Sprint name and origin board id are required. Start date, end date, and goal are optional.
+ *
+ * Note that the sprint name is trimmed. Also, when starting sprints from the UI, the "endDate" set through this call is
+ * ignored and instead the last sprint's duration is used to fill the form.
+ */
+export async function createSprint(client: Client, parameters: CreateSprint): Promise {
+ const config: SendRequestOptions = {
+ url: '/rest/agile/1.0/sprint',
+ method: 'POST',
+ body: {
+ endDate: parameters.endDate,
+ goal: parameters.goal,
+ name: parameters.name,
+ originBoardId: parameters.originBoardId,
+ startDate: parameters.startDate,
+ },
+ schema: SprintSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the sprint for a given sprint ID. The sprint will only be returned if the user can view the board that the
+ * sprint was created on, or view at least one of the issues in the sprint.
+ */
+export async function getSprint(client: Client, parameters: GetSprint): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}`,
+ method: 'GET',
+ schema: SprintSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Performs a partial update of a sprint. A partial update means that fields not present in the request JSON will not be
+ * updated.
+ *
+ * Notes:
+ *
+ * - For closed sprints, only the name and goal can be updated; changes to other fields will be ignored.
+ * - A sprint can be started by updating the state to 'active'. This requires the sprint to be in the 'future' state and
+ * have a startDate and endDate set.
+ * - A sprint can be completed by updating the state to 'closed'. This action requires the sprint to be in the 'active'
+ * state. This sets the completeDate to the time of the request.
+ * - Other changes to state are not allowed.
+ * - The completeDate field cannot be updated manually.
+ */
+export async function partiallyUpdateSprint(client: Client, parameters: PartiallyUpdateSprint): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}`,
+ method: 'POST',
+ body: {
+ completeDate: parameters.completeDate,
+ createdDate: parameters.createdDate,
+ endDate: parameters.endDate,
+ goal: parameters.goal,
+ id: parameters.id,
+ name: parameters.name,
+ originBoardId: parameters.originBoardId,
+ startDate: parameters.startDate,
+ state: parameters.state,
+ },
+ schema: SprintSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Performs a full update of a sprint. A full update means that the result will be exactly the same as the request body.
+ * Any fields not present in the request JSON will be set to null.
+ *
+ * Notes:
+ *
+ * - For closed sprints, only the name and goal can be updated; changes to other fields will be ignored.
+ * - A sprint can be started by updating the state to 'active'. This requires the sprint to be in the 'future' state and
+ * have a startDate and endDate set.
+ * - A sprint can be completed by updating the state to 'closed'. This action requires the sprint to be in the 'active'
+ * state. This sets the completeDate to the time of the request.
+ * - Other changes to state are not allowed.
+ * - The completeDate field cannot be updated manually.
+ */
+export async function updateSprint(client: Client, parameters: UpdateSprint): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}`,
+ method: 'PUT',
+ body: {
+ completeDate: parameters.completeDate,
+ createdDate: parameters.createdDate,
+ endDate: parameters.endDate,
+ goal: parameters.goal,
+ id: parameters.id,
+ name: parameters.name,
+ originBoardId: parameters.originBoardId,
+ startDate: parameters.startDate,
+ state: parameters.state,
+ },
+ schema: SprintSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/** Deletes a sprint. Once a sprint is deleted, all open issues in the sprint will be moved to the backlog. */
+export async function deleteSprint(client: Client, parameters: DeleteSprint): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns all issues in a sprint, for a given sprint ID. This only includes issues that the user has permission to
+ * view. By default, the returned issues are ordered by rank.
+ */
+export async function getIssuesForSprint(client: Client, parameters: GetIssuesForSprint): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/issue`,
+ method: 'GET',
+ searchParams: {
+ startAt: parameters.startAt,
+ maxResults: parameters.maxResults,
+ jql: parameters.jql,
+ validateQuery: parameters.validateQuery,
+ fields: parameters.fields,
+ expand: parameters.expand,
+ },
+ schema: SearchResultsSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Moves issues to a sprint, for a given sprint ID. Issues can only be moved to open or active sprints. The maximum
+ * number of issues that can be moved in one operation is 50.
+ */
+export async function moveIssuesToSprintAndRank(client: Client, parameters: MoveIssuesToSprintAndRank): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/issue`,
+ method: 'POST',
+ body: {
+ issues: parameters.issues,
+ rankAfterIssue: parameters.rankAfterIssue,
+ rankBeforeIssue: parameters.rankBeforeIssue,
+ rankCustomFieldId: parameters.rankCustomFieldId,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the keys of all properties for the sprint identified by the id. The user who retrieves the property keys is
+ * required to have permissions to view the sprint.
+ */
+export async function getPropertiesKeys(client: Client, parameters: GetPropertiesKeys): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/properties`,
+ method: 'GET',
+ schema: PropertyKeysSchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Returns the value of the property with a given key from the sprint identified by the provided id. The user who
+ * retrieves the property is required to have permissions to view the sprint.
+ */
+export async function getProperty(client: Client, parameters: GetProperty): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/properties/${parameters.propertyKey}`,
+ method: 'GET',
+ schema: EntityPropertySchema,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Sets the value of the specified sprint's property.
+ *
+ * You can use this resource to store a custom data against the sprint identified by the id. The user who stores the
+ * data is required to have permissions to modify the sprint.
+ */
+export async function setProperty(client: Client, parameters: SetProperty): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/properties/${parameters.propertyKey}`,
+ method: 'PUT',
+ body: parameters.propertyValue,
+ };
+
+ return await client.sendRequest(config);
+}
+
+/**
+ * Removes the property from the sprint identified by the id. Ths user removing the property is required to have
+ * permissions to modify the sprint.
+ */
+export async function deleteProperty(client: Client, parameters: DeleteProperty): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/properties/${parameters.propertyKey}`,
+ method: 'DELETE',
+ };
+
+ return await client.sendRequest(config);
+}
+
+/** Swap the position of the sprint with the second sprint. */
+export async function swapSprint(client: Client, parameters: SwapSprint): Promise {
+ const config: SendRequestOptions = {
+ url: `/rest/agile/1.0/sprint/${parameters.sprintId}/swap`,
+ method: 'POST',
+ body: {
+ sprintToSwapWith: parameters.sprintToSwapWith,
+ },
+ };
+
+ return await client.sendRequest(config);
+}
diff --git a/packages/agile/src/createAgileClient.ts b/packages/agile/src/createAgileClient.ts
new file mode 100644
index 0000000000..80ac71ea7b
--- /dev/null
+++ b/packages/agile/src/createAgileClient.ts
@@ -0,0 +1,380 @@
+import { type ClientConfig, createClient } from '@jira.js/base';
+import * as backlog from '#/api/backlog';
+import * as board from '#/api/board';
+import * as epic from '#/api/epic';
+import * as issue from '#/api/issue';
+import * as sprint from '#/api/sprint';
+import * as developmentInformation from '#/api/developmentInformation';
+import * as featureFlags from '#/api/featureFlags';
+import * as deployments from '#/api/deployments';
+import * as builds from '#/api/builds';
+import * as remoteLinks from '#/api/remoteLinks';
+import * as securityInformation from '#/api/securityInformation';
+import * as operations from '#/api/operations';
+import * as devopsComponents from '#/api/devopsComponents';
+import {
+ type MoveIssuesToBacklog,
+ type MoveIssuesToBacklogForBoard,
+ type GetAllBoards,
+ type CreateBoard,
+ type GetBoardByFilterId,
+ type GetBoard,
+ type DeleteBoard,
+ type GetIssuesForBacklog,
+ type GetConfiguration,
+ type GetEpics,
+ type GetIssuesWithoutEpicForBoard,
+ type GetBoardIssuesForEpic,
+ type GetFeaturesForBoard,
+ type ToggleFeatures,
+ type GetIssuesForBoard,
+ type MoveIssuesToBoard,
+ type GetProjects,
+ type GetProjectsFull,
+ type GetBoardPropertyKeys,
+ type GetBoardProperty,
+ type SetBoardProperty,
+ type DeleteBoardProperty,
+ type GetAllQuickFilters,
+ type GetQuickFilter,
+ type GetReportsForBoard,
+ type GetAllSprints,
+ type GetBoardIssuesForSprint,
+ type GetAllVersions,
+ type GetIssuesWithoutEpic,
+ type RemoveIssuesFromEpic,
+ type GetEpic,
+ type PartiallyUpdateEpic,
+ type GetIssuesForEpic,
+ type MoveIssuesToEpic,
+ type RankEpics,
+ type RankIssues,
+ type GetIssue,
+ type GetIssueEstimationForBoard,
+ type EstimateIssueForBoard,
+ type CreateSprint,
+ type GetSprint,
+ type PartiallyUpdateSprint,
+ type UpdateSprint,
+ type DeleteSprint,
+ type GetIssuesForSprint,
+ type MoveIssuesToSprintAndRank,
+ type GetPropertiesKeys,
+ type GetProperty,
+ type SetProperty,
+ type DeleteProperty,
+ type SwapSprint,
+ type StoreDevelopmentInformation,
+ type GetRepository,
+ type DeleteRepository,
+ type DeleteByProperties,
+ type ExistsByProperties,
+ type DeleteEntity,
+ type SubmitFeatureFlags,
+ type DeleteFeatureFlagsByProperty,
+ type GetFeatureFlagById,
+ type DeleteFeatureFlagById,
+ type SubmitDeployments,
+ type DeleteDeploymentsByProperty,
+ type GetDeploymentByKey,
+ type DeleteDeploymentByKey,
+ type GetDeploymentGatingStatusByKey,
+ type SubmitBuilds,
+ type DeleteBuildsByProperty,
+ type GetBuildByKey,
+ type DeleteBuildByKey,
+ type SubmitRemoteLinks,
+ type DeleteRemoteLinksByProperty,
+ type GetRemoteLinkById,
+ type DeleteRemoteLinkById,
+ type SubmitWorkspaces,
+ type DeleteLinkedWorkspaces,
+ type GetLinkedWorkspaceById,
+ type SubmitVulnerabilities,
+ type DeleteVulnerabilitiesByProperty,
+ type GetVulnerabilityById,
+ type DeleteVulnerabilityById,
+ type SubmitOperationsWorkspaces,
+ type DeleteWorkspaces,
+ type GetWorkspaces,
+ type SubmitEntity,
+ type DeleteEntityByProperty,
+ type GetIncidentById,
+ type DeleteIncidentById,
+ type GetReviewById,
+ type DeleteReviewById,
+ type SubmitComponents,
+ type DeleteComponentsByProperty,
+ type GetComponentById,
+ type DeleteComponentById,
+} from '#/parameters';
+import {
+ type GetAllBoards as GetAllBoardsModel,
+ type CreateBoard as CreateBoardModel,
+ type GetBoardByFilterId as GetBoardByFilterIdModel,
+ type GetBoard as GetBoardModel,
+ type SearchResults,
+ type GetConfiguration as GetConfigurationModel,
+ type GetEpics as GetEpicsModel,
+ type GetFeaturesForBoard as GetFeaturesForBoardModel,
+ type ToggleFeatures as ToggleFeaturesModel,
+ type GetProjects as GetProjectsModel,
+ type GetProjectsFull as GetProjectsFullModel,
+ type PropertyKeys,
+ type EntityProperty,
+ type GetAllQuickFilters as GetAllQuickFiltersModel,
+ type GetQuickFilter as GetQuickFilterModel,
+ type GetReportsForBoard as GetReportsForBoardModel,
+ type GetAllSprints as GetAllSprintsModel,
+ type GetAllVersions as GetAllVersionsModel,
+ type Epic,
+ type Issue,
+ type GetIssueEstimationForBoard as GetIssueEstimationForBoardModel,
+ type EstimateIssueForBoard as EstimateIssueForBoardModel,
+ type Sprint,
+ type StoreDevelopmentInformation as StoreDevelopmentInformationModel,
+ type GetRepository as GetRepositoryModel,
+ type ExistsByProperties as ExistsByPropertiesModel,
+ type SubmitFeatureFlags as SubmitFeatureFlagsModel,
+ type GetFeatureFlagById as GetFeatureFlagByIdModel,
+ type SubmitDeployments as SubmitDeploymentsModel,
+ type GetDeploymentByKey as GetDeploymentByKeyModel,
+ type GetDeploymentGatingStatusByKey as GetDeploymentGatingStatusByKeyModel,
+ type SubmitBuilds as SubmitBuildsModel,
+ type GetBuildByKey as GetBuildByKeyModel,
+ type SubmitRemoteLinks as SubmitRemoteLinksModel,
+ type GetRemoteLinkById as GetRemoteLinkByIdModel,
+ type GetLinkedWorkspaces,
+ type GetLinkedWorkspaceById as GetLinkedWorkspaceByIdModel,
+ type SubmitVulnerabilities as SubmitVulnerabilitiesModel,
+ type GetVulnerabilityById as GetVulnerabilityByIdModel,
+ type SubmitOperationsWorkspaces as SubmitOperationsWorkspacesModel,
+ type GetWorkspaces as GetWorkspacesModel,
+ type SubmitEntity as SubmitEntityModel,
+ type GetIncidentById as GetIncidentByIdModel,
+ type GetReviewById as GetReviewByIdModel,
+ type SubmitComponents as SubmitComponentsModel,
+ type GetComponentById as GetComponentByIdModel,
+} from '#/models';
+
+/**
+ * Creates a Jira Agile REST API client.
+ *
+ * @stable
+ *
+ * @example
+ * ```typescript
+ * import { createAgileClient } from '@jira.js/agile';
+ *
+ * const agile = createAgileClient({
+ * host: 'https://your-domain.atlassian.net',
+ * auth: { type: 'basic', email: 'you@example.com', apiToken: 'TOKEN' },
+ * });
+ *
+ * const boards = await agile.board.getAllBoards({ type: 'scrum' });
+ * ```
+ */
+export function createAgileClient(clientConfig: ClientConfig) {
+ const client = createClient(clientConfig);
+
+ return {
+ backlog: {
+ moveIssuesToBacklog: (parameters: MoveIssuesToBacklog): Promise =>
+ backlog.moveIssuesToBacklog(client, parameters),
+ moveIssuesToBacklogForBoard: (parameters: MoveIssuesToBacklogForBoard): Promise =>
+ backlog.moveIssuesToBacklogForBoard(client, parameters),
+ },
+ board: {
+ getAllBoards: (parameters?: GetAllBoards): Promise => board.getAllBoards(client, parameters),
+ createBoard: (parameters: CreateBoard): Promise => board.createBoard(client, parameters),
+ getBoardByFilterId: (parameters: GetBoardByFilterId): Promise =>
+ board.getBoardByFilterId(client, parameters),
+ getBoard: (parameters: GetBoard): Promise => board.getBoard(client, parameters),
+ deleteBoard: (parameters: DeleteBoard): Promise => board.deleteBoard(client, parameters),
+ getIssuesForBacklog: (parameters: GetIssuesForBacklog): Promise =>
+ board.getIssuesForBacklog(client, parameters),
+ getConfiguration: (parameters: GetConfiguration): Promise =>
+ board.getConfiguration(client, parameters),
+ getEpics: (parameters: GetEpics): Promise => board.getEpics(client, parameters),
+ getIssuesWithoutEpicForBoard: (parameters: GetIssuesWithoutEpicForBoard): Promise =>
+ board.getIssuesWithoutEpicForBoard(client, parameters),
+ getBoardIssuesForEpic: (parameters: GetBoardIssuesForEpic): Promise =>
+ board.getBoardIssuesForEpic(client, parameters),
+ getFeaturesForBoard: (parameters: GetFeaturesForBoard): Promise =>
+ board.getFeaturesForBoard(client, parameters),
+ toggleFeatures: (parameters: ToggleFeatures): Promise =>
+ board.toggleFeatures(client, parameters),
+ getIssuesForBoard: (parameters: GetIssuesForBoard): Promise =>
+ board.getIssuesForBoard(client, parameters),
+ moveIssuesToBoard: (parameters: MoveIssuesToBoard): Promise => board.moveIssuesToBoard(client, parameters),
+ getProjects: (parameters: GetProjects): Promise => board.getProjects(client, parameters),
+ getProjectsFull: (parameters: GetProjectsFull): Promise =>
+ board.getProjectsFull(client, parameters),
+ getBoardPropertyKeys: (parameters: GetBoardPropertyKeys): Promise =>
+ board.getBoardPropertyKeys(client, parameters),
+ getBoardProperty: (parameters: GetBoardProperty): Promise =>
+ board.getBoardProperty(client, parameters),
+ setBoardProperty: (parameters: SetBoardProperty): Promise => board.setBoardProperty(client, parameters),
+ deleteBoardProperty: (parameters: DeleteBoardProperty): Promise =>
+ board.deleteBoardProperty(client, parameters),
+ getAllQuickFilters: (parameters: GetAllQuickFilters): Promise =>
+ board.getAllQuickFilters(client, parameters),
+ getQuickFilter: (parameters: GetQuickFilter): Promise =>
+ board.getQuickFilter(client, parameters),
+ getReportsForBoard: (parameters: GetReportsForBoard): Promise =>
+ board.getReportsForBoard(client, parameters),
+ getAllSprints: (parameters: GetAllSprints): Promise =>
+ board.getAllSprints(client, parameters),
+ getBoardIssuesForSprint: (parameters: GetBoardIssuesForSprint): Promise =>
+ board.getBoardIssuesForSprint(client, parameters),
+ getAllVersions: (parameters: GetAllVersions): Promise =>
+ board.getAllVersions(client, parameters),
+ },
+ epic: {
+ getIssuesWithoutEpic: (parameters?: GetIssuesWithoutEpic): Promise =>
+ epic.getIssuesWithoutEpic(client, parameters),
+ removeIssuesFromEpic: (parameters: RemoveIssuesFromEpic): Promise =>
+ epic.removeIssuesFromEpic(client, parameters),
+ getEpic: (parameters: GetEpic): Promise => epic.getEpic(client, parameters),
+ partiallyUpdateEpic: (parameters: PartiallyUpdateEpic): Promise =>
+ epic.partiallyUpdateEpic(client, parameters),
+ getIssuesForEpic: (parameters: GetIssuesForEpic): Promise =>
+ epic.getIssuesForEpic(client, parameters),
+ moveIssuesToEpic: (parameters: MoveIssuesToEpic): Promise => epic.moveIssuesToEpic(client, parameters),
+ rankEpics: (parameters: RankEpics): Promise => epic.rankEpics(client, parameters),
+ },
+ issue: {
+ rankIssues: (parameters: RankIssues): Promise => issue.rankIssues(client, parameters),
+ getIssue: (parameters: GetIssue): Promise => issue.getIssue(client, parameters),
+ getIssueEstimationForBoard: (parameters: GetIssueEstimationForBoard): Promise =>
+ issue.getIssueEstimationForBoard(client, parameters),
+ estimateIssueForBoard: (parameters: EstimateIssueForBoard): Promise =>
+ issue.estimateIssueForBoard(client, parameters),
+ },
+ sprint: {
+ createSprint: (parameters: CreateSprint): Promise => sprint.createSprint(client, parameters),
+ getSprint: (parameters: GetSprint): Promise => sprint.getSprint(client, parameters),
+ partiallyUpdateSprint: (parameters: PartiallyUpdateSprint): Promise =>
+ sprint.partiallyUpdateSprint(client, parameters),
+ updateSprint: (parameters: UpdateSprint): Promise => sprint.updateSprint(client, parameters),
+ deleteSprint: (parameters: DeleteSprint): Promise => sprint.deleteSprint(client, parameters),
+ getIssuesForSprint: (parameters: GetIssuesForSprint): Promise =>
+ sprint.getIssuesForSprint(client, parameters),
+ moveIssuesToSprintAndRank: (parameters: MoveIssuesToSprintAndRank): Promise =>
+ sprint.moveIssuesToSprintAndRank(client, parameters),
+ getPropertiesKeys: (parameters: GetPropertiesKeys): Promise =>
+ sprint.getPropertiesKeys(client, parameters),
+ getProperty: (parameters: GetProperty): Promise => sprint.getProperty(client, parameters),
+ setProperty: (parameters: SetProperty): Promise