Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3bf1f42
chore: checkpoint monorepo + cloud v2 work in progress
MrRefactoring Jul 16, 2026
9996c89
feat!: single confluence.js package with both API versions on one core
MrRefactoring Jul 16, 2026
0df018b
fix: restore the v2 schema cleanup and clean the core
MrRefactoring Jul 16, 2026
f9cbea7
ci: split package checks out, and move live tests off every push
MrRefactoring Jul 16, 2026
757d99b
docs: rewrite README for 3.0 and add the migration guide and codemod
MrRefactoring Jul 16, 2026
f219aa3
chore: add the 3.0.0 changeset and fix the release workflows for one …
MrRefactoring Jul 16, 2026
0e105ef
test(v1): start the live suite — users, search, group, settings
MrRefactoring Jul 16, 2026
af29f8c
fix(v1): carry an inline oneOf request body through as a union
MrRefactoring Jul 16, 2026
56f4419
test(v1): cover labels, restrictions and watches; fix two more defects
MrRefactoring Jul 17, 2026
19de457
v1 sends the XSRF header itself
MrRefactoring Jul 17, 2026
66f7119
docs: state the v1 posture — follow the spec, move off it
MrRefactoring Jul 17, 2026
6732a04
fix(core): return the bytes for binary downloads
MrRefactoring Jul 17, 2026
cf117bf
fix(v1): six correctness fixes the live suite found
MrRefactoring Jul 17, 2026
85516a4
test(v1): finish the live suite — all 28 namespaces
MrRefactoring Jul 17, 2026
74668f2
docs: attachment upload works through the typed client now
MrRefactoring Jul 17, 2026
62fbe75
fix: absolutize Atlassian's doc links, export two reachable types
MrRefactoring Jul 17, 2026
390bcd6
docs: the documentation site
MrRefactoring Jul 17, 2026
e6c43d6
fix: repair the pre-push hook
MrRefactoring Jul 17, 2026
8de4b18
fix: repair a fresh install under pnpm 11
MrRefactoring Jul 17, 2026
96c78d9
docs: reword the guides, migration notes and comments
MrRefactoring Jul 17, 2026
e94d5a2
build: move to the TypeScript 7 compiler, and refresh the toolchain
MrRefactoring Jul 17, 2026
f182115
docs: self-host the logo, split the wordmark from the favicon
MrRefactoring Jul 17, 2026
6d0945b
chore(deps): bump @types/node to 22.20.1 (patch, stays on 22)
MrRefactoring Jul 17, 2026
9f9ae00
ci: move the workflows onto current action majors
MrRefactoring Jul 19, 2026
662a1a9
build: publish the dev channel as 3.0.0-dev.N
MrRefactoring Jul 19, 2026
9ae7740
feat!: rebuild OAuth 2.0 (3LO) on a typed error hierarchy
MrRefactoring Jul 19, 2026
d455288
docs: document OAuth 2.0 against what the API actually does
MrRefactoring Jul 19, 2026
a41e605
style: apply the formatter to untouched sources
MrRefactoring Jul 19, 2026
5951df7
fix!: make the runtime and the types browser-safe
MrRefactoring Jul 19, 2026
538706f
build: ship a browser bundle and gate compatibility in CI
MrRefactoring Jul 19, 2026
89e369d
build: set the version baseline back to the published 2.1.0
MrRefactoring Jul 19, 2026
c243e4e
docs: document running in a browser
MrRefactoring Jul 19, 2026
d35cace
docs: add a playground for the OAuth 2.0 flow
MrRefactoring Jul 19, 2026
406b476
fix: regenerate with the core fixes
MrRefactoring Jul 19, 2026
6ba8f9a
style: separate statement groups in the v1 and v2 sources
MrRefactoring Jul 20, 2026
b15f6b9
docs: name SchemaMismatchError as the response-drift error
MrRefactoring Jul 20, 2026
f05c64b
fix: correct the case of two v1 model filenames
MrRefactoring Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 24 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changesets

This directory is used by [@changesets/cli](https://github.com/changesets/changesets) to manage versioning and changelog entries for confluence.js.

## Workflow

1. **Make changes** — implement your feature or fix
2. **Add a changeset** — `pnpm changeset` (interactive prompt)
3. **Commit** — include the new `.md` file in your commit
4. **Release** — maintainers run `pnpm changeset:version` to bump versions, then publish

## Changeset types

| Type | When |
|------|------|
| `major` | Breaking changes |
| `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
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -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": []
}
119 changes: 119 additions & 0 deletions .changeset/tall-cameras-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
'confluence.js': major
---

**confluence.js 3.0 — both Confluence Cloud APIs from one package.**

The library now covers Cloud REST **v2** (30 namespaces, 218 methods) alongside
**v1** (28 namespaces, 130 methods). Both are first-class: Atlassian has not
deprecated v1, and the two cover different ground. One `host` and one client
serve both — the API path belongs to the request, not to your config.

Runtime dependencies are down to `zod` alone. axios, form-data, oauth and
atlassian-jwt are gone with the old transport, which is now the built-in `fetch`.
Every response is validated against a Zod 4 schema, so API drift raises a
`SchemaMismatchError` instead of surfacing as `undefined` later on.

```ts
import { createV2Client } from 'confluence.js';

const confluence = createV2Client({
host: 'https://your-domain.atlassian.net',
auth: { type: 'basic', email, apiToken },
});

await confluence.page.getPages({ spaceId: ['123'] });
```

Or import just what you call — the package is ESM-only and tree-shakable, with
entry points at `confluence.js/v1`, `confluence.js/v2` and `confluence.js/core`.

**OAuth 2.0 (3LO)** is a first-class strategy. Hand over your app credentials and
a refresh token, and the client refreshes before expiry, retries once on a `401`,
resolves the cloud id, and routes through the Atlassian gateway — where 3LO tokens
are actually accepted:

```ts
const confluence = createV2Client({
auth: {
type: 'oauth2',
clientId,
clientSecret,
refreshToken,
onTokenRefresh: ({ refreshToken }) => tokenStore.save(refreshToken),
},
});
```

The flow itself is covered too, so no separate OAuth library is needed:
`generateAuthorizationUrl`, `parseCallbackUrl`, `exchangeAuthorizationCode`,
`refreshOAuth2Token` and `getAccessibleResources`.

Both version factories accept an existing client, so v1 and v2 can share one
OAuth token — building one each would make the first refresh invalidate the
other's rotating refresh token:

```ts
const client = createClient({ auth: { type: 'oauth2', … } });

const v1 = createV1Client(client);
const v2 = createV2Client(client);
```

**Node and the browser, from one build.** There is no separate web build: the code
branches on what the runtime supports rather than on which runtime it is. It loads
from a dependency-resolving CDN as published, and a self-contained `dist/browser.js`
with zod inlined covers plain file hosts and `<script type="module">`:

```html
<script type="module">
import { createV2Client } from 'https://esm.sh/confluence.js';
</script>
```

Attachments accept the same values everywhere — a `File`, `Blob`, `Uint8Array`,
string, stream, and still Node's `Buffer` and `Readable`, which are a `Uint8Array`
and an `AsyncIterable` respectively. They are no longer *named* in `AttachmentContent`,
so the shipped declarations compile in a project without `@types/node`. Where a
browser cannot send a request body as a stream, the stream is read into a `Blob`
rather than failing. See the [browser guide](https://mrrefactoring.github.io/confluence.js/guide/browser).

**Every failure has a type.** Non-2xx responses throw an `ApiError` subclass —
`AuthError`, `ForbiddenError`, `NotFoundError`, `RateLimitError` (with
`retryAfterMs` parsed from `Retry-After`), `ServerError`. Transport faults throw
`NetworkError` instead of leaking a raw `fetch` `TypeError`, and OAuth failures
throw `OAuthError`. A 2xx whose body does not match the schema throws
`SchemaMismatchError`, carrying the raw `body` and, when the shape drifted, the
underlying `ZodError` on `cause`. Each ships an `isXxx` predicate that checks a branded marker
rather than the prototype chain, so narrowing survives two copies of the package
in one `node_modules`:

```ts
import { isNotFoundError, isRateLimitError } from 'confluence.js';

if (isNotFoundError(error)) return null;
if (isRateLimitError(error)) await sleep(error.retryAfterMs ?? 60_000);
```

### Breaking changes

- `new ConfluenceClient({…})` → `createV1Client({…})` / `createV2Client({…})`
- `authentication: { basic: {…} }` → `auth: { type: 'basic', … }`; `oauth2` → `auth: { type: 'oauth2', … }`
- JWT (Atlassian Connect) authentication is not supported — Connect apps should stay on `confluence.js@2`
- Callbacks, `middlewares`, `apiPrefix` and `baseRequestConfig` are removed
- `noCheckAtlassianToken` is removed with nothing to replace it: v1 enforces XSRF
on every write, so every v1 write now sends `X-Atlassian-Token: no-check` itself
- `BaseClient` and the per-namespace classes are gone, as are deep imports like `confluence.js/api/content`
- Errors are `ApiError` and its subclasses (`status`, `statusText`, `body`) rather
than `AxiosError`; transport faults are `NetworkError`, OAuth failures are `OAuthError`
- `AttachmentContent` no longer names `Buffer` or `Readable`. Both still work — the
type describes them structurally — but a declaration that required `@types/node`
broke every browser consumer
- `createMultipartRequestBody` returns a `Promise`: collecting a stream for a runtime
that cannot send one is asynchronous
- The v1 surface follows Atlassian's current spec, which has dropped 37 operations
(`getContent`, `createContent`, `getSpace`, …) since 2.x shipped — each is mapped
to its v2 equivalent in the migration guide

See [MIGRATION.md](https://github.com/MrRefactoring/confluence.js/blob/master/MIGRATION.md);
a codemod handles the mechanical parts.
65 changes: 65 additions & 0 deletions .github/workflows/changeset-check.yml
Original file line number Diff line number Diff line change
@@ -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@v7
with:
fetch-depth: 0

- name: ⚙️ Use Node.js 22.x
uses: actions/setup-node@v7
with:
node-version: 22.x

- name: 📦 Install pnpm
uses: pnpm/action-setup@v6

- 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 for guidance."
echo ""
exit 1
fi

echo "✅ Changeset found:"
echo "$CHANGESETS"
121 changes: 48 additions & 73 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,114 +3,89 @@ name: 🛠️ CI
on:
push:
branches:
- '**' # Runs on all branch pushes
- '**'
tags-ignore:
- '**' # Ignore all tag pushes
- '**'
repository_dispatch:
types: [ pr-approved ]

env:
PNPM_VERSION: '10'
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: 🏗️ Build
name: 🏗️ Build & lint
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
uses: actions/checkout@v7
- name: ⚙️ Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
- name: 📦 Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 📌 Installing dependencies
uses: pnpm/action-setup@v6
- name: 📌 Install dependencies
run: pnpm install --frozen-lockfile
- name: 🛠️ Building sources
- name: 🛠️ Build
run: pnpm run build

lint:
name: 🔍 Lint Code
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x]
steps:
- name: 🔄 Checkout sources
uses: actions/checkout@v4
- name: ⚙️ Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: 📦 Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 📌 Installing dependencies
run: pnpm install --frozen-lockfile
- name: ✨ Linting
- name: 🔍 Lint
run: pnpm run lint
env:
CI: true
- name: 🧾 Typecheck (sources and tests)
run: pnpm run typecheck

test_unit:
name: 🧪 Unit Tests
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
uses: actions/checkout@v7
- name: ⚙️ Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
- name: 📦 Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 📌 Installing dependencies
uses: pnpm/action-setup@v6
- name: 📌 Install dependencies
run: pnpm install --frozen-lockfile
- name: 🚀 Running unit tests
run: pnpm run test:unit
- name: 🧪 Unit tests
run: pnpm run test

test_integration:
name: 🧩 Integration Tests
needs:
- lint
- test_unit
package:
name: 📦 Package checks
needs: build
runs-on: ubuntu-latest
strategy:
max-parallel: 1
matrix:
node-version: [20.x, 22.x]
steps:
- name: 🔄 Checkout sources
uses: actions/checkout@v4
- name: ⚙️ Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/checkout@v7
- name: ⚙️ Use Node.js 22.x
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
node-version: 22.x
- name: 📦 Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 📌 Installing dependencies
uses: pnpm/action-setup@v6
- 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
run: pnpm run build
- name: 🔎 Are the types wrong?
run: pnpm run check:attw
- name: 🚚 Consumer smoke tests
run: pnpm run check:consumers
# Static: nothing Node-only is named in the runtime or in the declarations.
- name: 🌐 Browser-safe build
run: pnpm run check:browser
# Live: the built package actually loads and works in a browser. Kept in
# this job so the browser download only happens once per run.
- name: 🎭 Install Chromium
run: pnpm exec playwright install --with-deps chromium
- name: 🌍 Browser smoke tests
run: pnpm run check:browser:e2e
Loading