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
56 changes: 56 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Changesets

This folder holds the pending release notes for the next version.

Whenever you change something worth mentioning in a release, run `npx changeset`
and answer the two prompts (which packages, and whether it's a patch/minor/major).
That writes a small markdown file here, which you commit along with your changes.

At release time `npm run publish` consumes every pending file: it computes the
resulting versions, updates the `package.json`s and the `CHANGELOG.md`s, and
deletes the files.

## Each package versions on its own

There is no group here: the ten packages are **independent**, so a changeset
touching `datocms-structured-text-to-markdown` bumps that package and nothing
else. This is what `lerna.json` asked for in "Switch lerna to independent
versioning", and it is what the repository now actually does — the ten packages
sitting at `6.0.0` are the residue of the fixed mode that came before it.

So the package list inside a changeset carries real weight, unlike in a lockstep
repo. Pick every package whose _own_ behaviour changed. You do **not** need to
list packages that merely depend on one you changed: changesets bumps a
dependent by itself, whenever the new version falls outside the range that
dependent declares.

`datocms-structured-text-utils` is the root of the graph — everything else
depends on it, directly or through `datocms-structured-text-generic-html-renderer`
— so a `major` there is a major for the whole repository in practice, even
though the version numbers will no longer move in lockstep to say so.

## Which bump level?

- `patch` — bug fixes only. It's the clearest signal in semver ("nothing new,
just a fix"), so we don't spend it on anything else.
- `minor` — new API surface. A new exported function, a new node type, a new
option on an existing one.
- `major` — something was removed or renamed.

## Prereleases

`npm run publish-next` publishes under the `next` dist-tag, leaving `latest`
untouched. It works in two modes:

- **as-is** — the pending changesets produce a normal version (say `6.1.0`)
which is published under `next` instead of `latest`;
- **real prerelease versions** — run `npx changeset pre enter next` first and
the same command produces `6.1.0-next.0`, `6.1.0-next.1`, … That mode is
recorded in `.changeset/pre.json`, which you commit. Run
`npx changeset pre exit` when the line is done.

Either way the GitHub release is marked as a prerelease, so it never becomes
the repository's "Latest release".

`npm run publish` refuses to run while `.changeset/pre.json` exists, so a
forgotten pre mode can't quietly turn a real release into a prerelease.
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@4.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"access": "public",
"baseBranch": "main",
"fixed": [],
"linked": [],
"updateInternalDependencies": "patch",
"ignore": []
}
15 changes: 11 additions & 4 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# Installs from the lockfile, builds every workspace through Turborepo and runs
# the test suite.
#
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI
Expand All @@ -15,16 +17,21 @@ jobs:

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
# 18 and 20 are both past end-of-life, and @changesets/cli needs
# ^22.11 || ^24 || >=26 — see .nvmrc.
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [22.x, 24.x]

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# One install for the whole workspace; the internal packages are
# symlinked into the root node_modules, so there is no bootstrap step.
- run: npm ci
- run: ./node_modules/.bin/lerna bootstrap
- run: npm run build --if-present
# Turborepo derives the build order from the manifests: `utils` before
# `generic-html-renderer` before the renderers that depend on it.
- run: npm run build
- run: npm test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ yarn.lock

**/.rpt2_cache
.vscode/settings.json

# Turborepo
.turbo
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
**/dist
**/package-lock.json
**/package-lock.json
.turbo
43 changes: 32 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,30 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Overview

This is a Lerna-managed monorepo for DatoCMS Structured Text (DAST) utilities. It provides TypeScript libraries for handling, converting, and rendering DatoCMS Structured Text documents across multiple formats.
This is an npm-workspaces monorepo for DatoCMS Structured Text (DAST) utilities, built with Turborepo and released with Changesets. It provides TypeScript libraries for handling, converting, and rendering DatoCMS Structured Text documents across multiple formats.

Node 22 or later is required (see `.nvmrc`) — `@changesets/cli` declares `^22.11 || ^24 || >=26` and dies on Node 20 with an error that does not mention versions. The published packages have no such requirement.

## Commands

### Building

```bash
npm run build # Bootstrap all packages and build them
lerna bootstrap # Install dependencies for all packages
lerna run build # Build all packages
npm install # One install for the whole workspace; no bootstrap step
npm run build # turbo run build — every package, in dependency order
npx turbo run build --filter=<package-name> # ...and its dependencies only
```

Individual packages can be built by navigating to `packages/<package-name>` and running:
Turborepo derives the order from the manifests (`dependsOn: ["^build"]`), so nobody maintains a list: `utils` builds before `generic-html-renderer` before the renderers that depend on it. `npm run <script> --workspaces` would **not** honour that order.

Individual packages can still be built by navigating to `packages/<package-name>` and running:

```bash
npm run build # Compiles TypeScript to both CommonJS (dist/cjs) and ESM (dist/esm)
```

The packages resolve each other through their built `dist/`, so a fresh checkout must be built before the tests will run.

### Testing

```bash
Expand All @@ -41,18 +47,28 @@ npm run prettier # Format all TypeScript and JSON files

Pre-commit hook automatically runs `pretty-quick --staged` to format staged files.

### Publishing
### Changesets and publishing

Every user-visible change needs a changeset, committed in the same PR:

```bash
npm run publish # Build, test, and publish to npm
npm run publish-next # Publish with 'next' dist-tag
npx changeset # Pick the changed packages and the bump level
```

The ten packages version **independently**: a changeset bumps exactly the packages it names, plus any dependent whose declared range the new version falls outside. So the package list inside a changeset carries real weight. `patch` is for bug fixes only; new API surface is `minor`. See `.changeset/README.md`.

```bash
npm run publish # Build, test, version, publish to npm, tag, release notes
npm run publish-next # The same, under the 'next' dist-tag
```

`bin/publish.mjs` implements it. The ordering is the point: everything fallible runs before anything irreversible, and npm is published before git is tagged, so a tag can never point at a version nobody can install. There is no rollback — every step is idempotent, so an interrupted release is resumed by re-running it. Tags are per package (`datocms-structured-text-utils@6.1.0`); the historical `vX.Y.Z` tags stay where they are.

## Architecture

### Monorepo Structure

The repository contains 9 packages in `packages/`:
The repository contains 10 packages in `packages/`:

**Core:**

Expand All @@ -74,6 +90,11 @@ The repository contains 9 packages in `packages/`:

- `slate-utils`: Slate.js integration helpers

**Other renderers/converters:**

- `to-markdown`: Markdown renderer
- `dastdown`: Markdown-flavoured serialization and parsing of DAST, with round-trip support

### DAST (DatoCMS Abstract Syntax Tree)

The structured text format follows a tree structure defined in `packages/utils/src/types.ts`:
Expand Down Expand Up @@ -141,7 +162,7 @@ The root `tsconfig.json` provides shared compiler options (strict mode, ES2015+

### Inter-package Dependencies

Most packages depend on `datocms-structured-text-utils` for core types and utilities. Lerna manages workspace linking during development. When publishing, packages reference specific versions of dependencies.
Most packages depend on `datocms-structured-text-utils` for core types and utilities, directly or through `generic-html-renderer`. npm workspaces symlinks them into the root `node_modules`, so a change in `utils` is visible to its dependents as soon as `utils` is rebuilt. When publishing, packages reference specific versions of dependencies.

## Development Notes

Expand Down Expand Up @@ -169,5 +190,5 @@ Most packages depend on `datocms-structured-text-utils` for core types and utili

**utils:**

- `update-links.js` script updates GitHub links in README.md to match current line numbers
- `update-links.js` script updates GitHub links in README.md to match current line numbers. It runs as part of this package's `build`, which is why `packages/utils/turbo.json` lists `README.md` as a build output alongside `dist/**` — otherwise turbo would treat a file the build writes as one of its own inputs.
- Tree manipulation supports custom type parameters for block/inline item types
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,41 @@ Monorepo with Typescript libraries for handling and rendering [DatoCMS Structure
- [Creating Structured Text fields using DatoCMS Rest API](https://www.datocms.com/docs/content-management-api/resources/field/create#creating-structured-text-fields)
- [Creating records with Structured Text fields using DatoCMS Rest API](https://www.datocms.com/docs/content-management-api/resources/item/create#structured-text-fields)

## Working on this repository

An npm-workspaces monorepo built with [Turborepo](https://turborepo.com/) and
released with [Changesets](https://github.com/changesets/changesets).

```sh
git clone https://github.com/datocms/structured-text && cd structured-text
npm install # one install for all ten packages; no bootstrap step
npm run build # turbo, in dependency order
npm test # eslint, then the Jest suite (448 tests, nothing external)
```

Node 22 or later — see `.nvmrc`. The published packages themselves have no such
requirement; it is the release tooling that does.

The packages resolve each other through their built `dist/`, so **run
`npm run build` before `npm test`** on a fresh checkout.

### Releasing (maintainers)

Every user-visible change needs a changeset: run `npx changeset` from the repo
root in the same PR, pick the packages that changed and the bump level (`patch`
is for bug fixes only, new API surface is `minor`), and commit the file it
writes under `.changeset/`. See [`.changeset/README.md`](.changeset/README.md)
for the details — in particular, the packages version **independently**, so
which ones you list matters.

To release, from an up-to-date, clean `main`, run `npm run publish` from the
repo root. It builds and tests, applies the pending changesets — bumping only
the packages that changed and writing their `CHANGELOG.md`s — publishes to npm,
and only then tags each published package `name@X.Y.Z`, pushes, and creates a
GitHub release per tag whose notes come straight from those changelog entries.
An interrupted release is resumed by re-running it, never undone. Use
`npm run publish-next` for a prerelease under the `next` dist-tag.

## License

This repository is published under the [MIT](LICENSE.md) license.
Expand All @@ -65,6 +100,7 @@ This repository is published under the [MIT](LICENSE.md) license.
**Building with AI:** [Agent Skills](https://www.datocms.com/docs/agent-skills) turn coding assistants (Claude Code, Cursor) into expert DatoCMS developers, with full read/write via the auto-installed CLI. No local terminal? Use the [MCP Server](https://www.datocms.com/docs/mcp-server) instead.

**Talking to DatoCMS from code:**

- [Content Delivery API](https://www.datocms.com/docs/content-delivery-api) (CDA) — the fast, read-only GraphQL API your website/app uses to **fetch** published content.
- [Content Management API](https://www.datocms.com/docs/content-management-api) (CMA) — the REST API for **creating and updating** content, models, and project settings (think scripts, migrations, integrations).
- [CLI](https://www.datocms.com/docs/scripting-migrations/installing-the-cli) — terminal tool for schema migrations and importing from Contentful/WordPress.
Expand All @@ -73,5 +109,4 @@ This repository is published under the [MIT](LICENSE.md) license.

**Want a head start?** Browse our [starter projects](https://www.datocms.com/marketplace/starters) — ready-to-deploy example sites for popular frameworks.


<!--datocms-autoinclude-footer end-->
Loading
Loading