diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..e5b98b44ff --- /dev/null +++ b/AGENTS.md @@ -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. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000000..f433c21d96 --- /dev/null +++ b/ARCHITECTURE.md @@ -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 | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..b2020d4e2e --- /dev/null +++ b/CONTRIBUTING.md @@ -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.