Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Agent Guide — contentful-ui-extensions-sdk / @contentful/app-sdk

## What This Repo Does
The App SDK (published as both `@contentful/app-sdk` and the legacy `contentful-ui-extensions-sdk`) is the core runtime library every Contentful app must use. It provides the `init()` entry point, PostMessage-based communication with the Contentful host application, and TypeScript types for all SDK locations.

## Ownership
`@contentful/team-marketplace` (full, co-owned with `@contentful/team-extensibility`)

## Structure

```
lib/ # TypeScript source — one file per API module
lib/types/ # All public TypeScript types and interfaces
test/unit/ # Mocha test specs — one per module
dist/ # Built UMD output (generated — do not edit)
scripts/ # Custom dual-publish scripts
```

See [ARCHITECTURE.md](ARCHITECTURE.md) for the full picture.

## Key Exports

| Export | Description |
|--------|-------------|
| `init(callback, options?)` | Entry point — connects app to host frame |
| `locations` | Enum of all valid app locations |
| `FieldAppSDK`, `SidebarAppSDK`, `PageAppSDK`, etc. | Location-specific SDK types |
| `AppExtensionSDK` | Config screen SDK type |
| `HomeExtensionSDK` | Home location SDK type |
| `DialogAppSDK` | Dialog SDK type |

## Published as Two Package Names
Same code, two npm packages:
- `@contentful/app-sdk` — modern scoped name (use this in new code)
- `contentful-ui-extensions-sdk` — legacy name (still live, do not remove)

Custom publish scripts in `scripts/publish.js` handle both. Semantic-release drives versioning.

## Sharp Edges & Invariants

- **UMD output, not ESM** — build target is UMD via Rollup. Do not assume tree-shaking; keep the bundle small.
- **Mocha, not Vitest** — tests use `ts-mocha` + Chai + Sinon + jsdom. Do not introduce Vitest.
- **No `any` in public types** — every exported type must be explicit. `unknown` is acceptable internally.
- **Breaking changes require major version** — this SDK has thousands of app consumers. Removing or renaming any public export is a breaking change.
- **Canary releases** from the `canary` branch publish as `X.Y.Z-alpha.N` under the `canary` dist-tag. Do not merge canary work to master until stable.
- **PostMessage channel only** — all SDK calls go through `channel.ts`. Do not add direct DOM access or `window` globals.

## Never / Always

- **Never** access `window.contentfulExtension` directly — that is the pre-SDK legacy pattern.
- **Never** remove or rename a public type export without bumping the major version.
- **Always** add a test in `test/unit/` for any new module or API surface.
- **Always** run `npm run check-types` before opening a PR — type errors in a types library are blocking.
- **Always** run `npm run size` after build changes — the gzip size limit is enforced in CI.
103 changes: 103 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Architecture — contentful-ui-extensions-sdk / @contentful/app-sdk

## Overview

The App SDK is the **communication bridge** between a Contentful app (running in an iframe) and the Contentful host application. It uses a PostMessage-based channel to expose Contentful data and actions to app code in a safe, sandboxed way.

## How It Works

```
Contentful Host App (parent frame)
│ window.postMessage (structured messages)
App iframe (app code calls sdk.field.getValue(), etc.)
└── lib/channel.ts (PostMessage send/receive abstraction)
└── lib/signal.ts (event subscription over a channel)
└── lib/initialize.ts (creates SDK instance)
└── lib/api.ts (routes to location-specific API objects)
```

## Module Structure

| Module | Role |
|--------|------|
| `lib/initialize.ts` | Connects to host frame; creates the SDK instance passed to `init()` callback |
| `lib/channel.ts` | PostMessage send/receive abstraction |
| `lib/signal.ts` | Event subscription over a channel |
| `lib/api.ts` | Factory — builds the correct SDK object for the current location |
| `lib/field.ts` | Field-level API (get/set value, validate) |
| `lib/field-locale.ts` | Field API with locale selection |
| `lib/entry.ts` | Entry-level API |
| `lib/editor.ts` | Entry editor API |
| `lib/app.ts` | App lifecycle hooks (`setReady`, `onConfigure`, `getParameters`) |
| `lib/window.ts` | Window/size control (`updateHeight`, `startAutoResizer`) |
| `lib/space.ts` | Space read/write API (wraps CMA calls through the channel) |
| `lib/dialogs.ts` | System dialog management |
| `lib/navigator.ts` | Host navigation API |
| `lib/cma.ts` + `lib/cmaAdapter.ts` | CMA adapter (passes CMA calls through the channel to the host) |
| `lib/locations.ts` | `locations` enum |
| `lib/types/` | All public TypeScript types — no runtime code |

## Location SDK Variants

`lib/api.ts` builds a different SDK object depending on which Contentful location the app is rendering in:

| Location | SDK Type |
|----------|----------|
| `entry-field` | `FieldAppSDK` |
| `entry-sidebar` | `SidebarAppSDK` |
| `entry-editor` | `EditorAppSDK` |
| `app-config` | `AppExtensionSDK` |
| `dialog` | `DialogAppSDK` |
| `page` | `PageAppSDK` |
| `home` | `HomeExtensionSDK` |

## Build Pipeline

```
lib/**/*.ts
→ Rollup (rollup.config.js)
→ dist/cf-extension-api.js (UMD, minified)
→ dist/cf-extension-api.bundled.js (CDN self-contained bundle)
→ tsc
→ dist/index.d.ts (TypeScript declarations)
```

- Target: ES5 (broad browser compatibility)
- Format: UMD (works in CommonJS and browser globals)
- Minification: Terser (production); source maps available via `build:debug`

## Dual-Package Publish

The same build is published under two npm package names:

1. `@contentful/app-sdk` — modern scoped name
2. `contentful-ui-extensions-sdk` — legacy name

`scripts/publish.js` temporarily mutates `package.json` `name` field and runs `npm publish` twice. Semantic-release triggers this on `master` and `canary` branches.

## CI / Release

```
Every commit (CircleCI):
unit → lint + test + build + size check

master/canary branches (after unit):
semantic-release → version bump → dual npm publish
```

- **`master`**: publishes `latest` tag
- **`canary`**: publishes `X.Y.Z-alpha.N` under `canary` dist-tag

## Key Dependencies

| Package | Role |
|---------|------|
| `contentful-management` | Only production dependency — CMA adapter types |
| `rollup` + `terser` | Bundler + minifier |
| `ts-mocha` + `mocha` | Test runner |
| `chai` + `sinon` | Assertions + mocking |
| `jsdom` | Browser DOM simulation in tests |
| `semantic-release` | Automated versioning + publish |
90 changes: 90 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Contributing to contentful-ui-extensions-sdk / @contentful/app-sdk

## Prerequisites

| Tool | Version |
|------|---------|
| Node.js | 18 (`.nvmrc`) |
| npm | bundled with Node |

## Setup

```bash
git clone https://github.com/contentful/ui-extensions-sdk.git
cd ui-extensions-sdk
npm ci
```

## Running Tests

```bash
npm test
```

Tests use **Mocha + ts-mocha** (not Vitest). Test files are in `test/unit/*.spec.ts`. Results are written to `test/unit/reports/` as JUnit XML.

## Building

```bash
npm run build
```

Compiles TypeScript and bundles with Rollup. Output goes to `dist/`. Run `npm run build:debug` for a build with source maps.

## Type Checking

```bash
npm run check-types
```

Run this before every PR — type errors in a types library are blocking. It's faster to catch locally than in CI.

## Linting

```bash
npm run lint
```

ESLint on `lib/` and `test/`.

## Bundle Size Check

```bash
npm run size
```

Checks gzip size of `dist/cf-extension-api.js`. CI fails if the size limit is exceeded. Run after adding new code or dependencies.

## Code Conventions

- **TypeScript only** — no plain `.js` files in `lib/`
- **No `any`** — use explicit types or `unknown`
- **No new production dependencies** without strong justification — the SDK must stay lean
- **Conventional Commits** — `feat:`, `fix:`, `chore:`, `docs:`
- **One test file per module** — `lib/foo.ts` → `test/unit/foo.spec.ts`

## Releasing

Releases are fully automated via semantic-release on the `master` branch. Do not manually bump versions or publish.

**Canary releases**: Merge to the `canary` branch. CI publishes `X.Y.Z-alpha.N` under the `canary` npm dist-tag. Install with:
```bash
npm install @contentful/app-sdk@canary
```

## Branch Strategy

- **`master`** — production; triggers semantic-release on merge
- **`canary`** — prerelease; publishes canary versions
- **Feature branches** — PR against `master`

## Troubleshooting

**Type errors after pulling**
Run `npm ci` — a dependency may have been updated.

**`dist/` is stale**
Run `npm run build` to regenerate.

**Test failures in `channel` or `signal` modules**
These depend on jsdom for PostMessage simulation. Ensure `npm ci` has been run and jsdom is installed.
Loading