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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ jobs:
# Installs from the committed lockfile, so CI matches local installs.
- run: npm ci

# Every package.json must declare the same version — releases are tagged
# from it, and a mismatch fails the release halfway through.
- run: npm run check:versions

- run: npm run lint

# Must precede the tests: the examples workspace resolves the testing
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ jobs:

- run: npm ci

# Guard: the tag must match the version both packages declare, so the
# Guard 1: every package.json agrees with every other one.
- run: npm run check:versions

# Guard 2: the tag must match the version both packages declare, so the
# release name can never disagree with the tarballs attached to it.
- name: Check package versions match the tag
run: |
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ npm workspaces monorepo:
- `npm run build` — tsup builds both packages **in dependency order**, which the root script spells out explicitly (`-w testing && -w fixture-generator`). Don't switch it back to `--workspaces`: npm runs workspaces in listed/glob order, not topological order, and the fixture generator's declaration build imports `FixtureData` from the testing package's built `dist/index.d.ts`. Building out of order fails with `TS2307: Cannot find module '@usdr/airtable-interface-testing'` — only on clean checkouts (CI), since a stale local `dist/` hides it. Build before running the testing package's dist smoke test or the CLI.
- `npm test` — every workspace's Jest suite. All suites must pass before committing.
- `npm run pack:release` — builds, then packs both tarballs into `release/` (gitignored). Always pack through this script: `npm pack` alone ships whatever stale `dist/` happens to be on disk.
- `npm run check:versions` — asserts every `package.json` (root, both packages, the example) declares the same version. Runs in CI before lint and again in the release workflow; a drift here is what breaks a release mid-flight. Bump with `npm version <v> --workspaces --include-workspace-root --no-git-tag-version` (it refuses when some packages already match — bump those with `-w <name>`).
- `npm run lint` — ESLint over all TypeScript sources (flat config in `eslint.config.mjs`; `.js`/`.cjs`/`.mjs` tool configs and generated `fixtures/` are ignored).
- **Build before test.** `npm test` requires a prior `npm run build`: the examples workspace resolves the testing package's Jest preset out of `dist/`, and the dist smoke test needs the built artifact. CI (`.github/workflows/ci.yml`) runs lint → build → test in that order on every PR and on pushes to `main`, across Node 20.19 and 22.
- `npx tsc` inside a workspace — type check (no emit).
Expand Down Expand Up @@ -66,6 +67,8 @@ Security rules adapted from [TikiTribe/claude-secure-coding-rules](https://githu

## Work log

- **2026-08-01** — All four `package.json` files bumped to 0.2.0 and kept in lockstep from now on: `scripts/check-versions.mjs` (root script `check:versions`) fails when any two disagree, wired into ci.yml before lint and release.yml before the tag guard. A `testing` 0.2.0 / `fixture-generator` 0.1.0 split is what broke the first release. Release-URL examples in all four docs moved to v0.2.0; the earlier "root stays at 0.0.0" guidance is retired.

- **2026-07-23** — Switched the pinned SDK from `interface-alpha-next` to `interface-alpha` (what Airtable's templates install). Required a real fix: `interface-alpha` doesn't export `./package.json`, so `sdk_internals.ts` gained `resolveSdkRoot()` with an entry-point fallback. Full suite verified green against both dist-tags.

- **2026-07-23** — Releases now trigger on merge: `.github/workflows/tag-on-merge.yml` tags `main` with the version from `packages/testing/package.json` (no-op when that tag exists) and invokes `release.yml`, which gained a `workflow_call` trigger. Documented the GITHUB_TOKEN-doesn't-trigger-workflows constraint that forces the direct call.
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You get a `TestDriver` with the same shape as the v1 library — fixture data in
These packages aren't on npm yet — we attach tarballs to [GitHub Releases](https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases) instead. Open the latest release, copy the link to the `.tgz` you want under **Assets**, and hand it to npm:

```bash
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.1.0/usdr-airtable-interface-testing-0.1.0.tgz
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.2.0/usdr-airtable-interface-testing-0.2.0.tgz
```

Release assets always follow the same shape, so you can bump the version in that URL directly:
Expand Down Expand Up @@ -61,13 +61,15 @@ A husky pre-commit hook runs `npm run lint` and the test suite before each commi
Releasing is a side effect of merging. Bump the `version` in both `packages/*/package.json` as part of your PR, and when it merges to `main` the [tag-on-merge workflow](.github/workflows/tag-on-merge.yml) tags the merge commit with that version and kicks off the release. Merges that don't touch the version do nothing — the tag already exists, so the workflow stops there.

```bash
npm version 0.2.0 -w @usdr/airtable-interface-testing -w @usdr/airtable-interface-testing-fixtures --no-git-tag-version
npm version 0.3.0 --workspaces --include-workspace-root --no-git-tag-version
```

That bumps both released packages — and leaves the private example alone — so you can commit the change in your PR and merge.
**Every `package.json` in the repo must carry the same version** — the root, both packages, and the example. CI fails the build if they drift, because a mismatch is what breaks a release halfway through. Run `npm run check:versions` locally to check.

If some packages are already at the target version, `npm version` refuses to run ("Version not changed"); bump the stragglers individually with `-w <package-name>`.

The [release workflow](.github/workflows/release.yml) then verifies the tag matches both package versions, runs lint, build, and the full test suite, packs both tarballs, and publishes a GitHub Release with generated notes and the tarballs attached. If anything fails, nothing is published.

**Note:** the version in `packages/testing/package.json` is what drives the tag. The root `package.json` is private, stays at `0.0.0`, and is never released.
**Note:** the version in `packages/testing/package.json` is what drives the tag. The root and example packages are private and never published, but they carry the same version so the check above can enforce one number across the repo.

You can still release by hand — push a `v*` tag yourself, or run the release workflow from the Actions tab against an existing tag. To build the tarballs locally without releasing, run `npm run pack:release` and look in `release/`.
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For a live example, check out the [Example extension](../examples/todo-list).
This package isn't on npm yet — we publish tarballs on [GitHub Releases](https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases) instead. After setting up your Airtable project, install the latest release:

```bash
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.1.0/usdr-airtable-interface-testing-0.1.0.tgz
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.2.0/usdr-airtable-interface-testing-0.2.0.tgz
```

**Finding the URL ---** open the [releases page](https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases), pick a release, and look under **Assets**. Copy the link address of `usdr-airtable-interface-testing-<version>.tgz` — that's the URL you pass to `npm install`. The URLs always follow the same pattern, so you can also just edit the version in the command above:
Expand Down
2 changes: 1 addition & 1 deletion examples/todo-list/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@usdr/example-todo-list",
"private": true,
"version": "0.1.0",
"version": "0.2.0",
"description": "Example Airtable interface extension with an automated test suite using @usdr/airtable-interface-testing",
"type": "module",
"scripts": {
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "npm test --workspaces --if-present",
"build": "npm run build -w @usdr/airtable-interface-testing && npm run build -w @usdr/airtable-interface-testing-fixtures",
"lint": "eslint .",
"check:versions": "node scripts/check-versions.mjs",
"pack:release": "npm run build && node -e \"require('fs').mkdirSync('release',{recursive:true})\" && npm pack -w @usdr/airtable-interface-testing -w @usdr/airtable-interface-testing-fixtures --pack-destination release",
"prepare": "husky"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/fixture-generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Generate [`FixtureData`](../testing/README.md#writing-fixture-data) for interfac
Inside this repo the CLI is already linked, so `npx airtable-testing-fixtures` works from the repo root. From another project, install the tarball attached to a [GitHub Release](https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases) — copy the link to `usdr-airtable-interface-testing-fixtures-<version>.tgz` under **Assets**:

```bash
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.1.0/usdr-airtable-interface-testing-fixtures-0.1.0.tgz
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.2.0/usdr-airtable-interface-testing-fixtures-0.2.0.tgz
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion packages/fixture-generator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@usdr/airtable-interface-testing-fixtures",
"version": "0.1.0",
"version": "0.2.0",
"description": "Generate test fixture data for Airtable interface extensions from a real base via the Airtable REST API",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion packages/testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The [example extension's test suite](../../examples/todo-list/test/app.test.tsx)
This package isn't published to npm. Install the tarball from a [GitHub Release](https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases):

```bash
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.1.0/usdr-airtable-interface-testing-0.1.0.tgz @airtable/blocks@interface-alpha
npm install --save-dev https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases/download/v0.2.0/usdr-airtable-interface-testing-0.2.0.tgz @airtable/blocks@interface-alpha
```

**Finding the URL ---** on the [releases page](https://github.com/usdigitalresponse/airtable-interface-extension-testing/releases), open a release and copy the link to `usdr-airtable-interface-testing-<version>.tgz` under **Assets**. Every release follows the same URL shape, so bumping the version in the command above works too:
Expand Down
74 changes: 74 additions & 0 deletions scripts/check-versions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env node
/**
* Fails if the workspaces disagree about the version.
*
* Releases are driven by the version in package.json, and a mismatch between
* packages produces tarballs whose names disagree with the tag — which is how
* a release fails halfway through. Catching it in CI keeps that off main.
*
* Workspaces are discovered from the root package.json, so a new package is
* covered without touching this script.
*/
import {existsSync, readFileSync, readdirSync} from 'node:fs';
import * as path from 'node:path';
import {fileURLToPath} from 'node:url';

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');

function readPackageJson(file) {
return JSON.parse(readFileSync(file, 'utf8'));
}

/** Expand the `dir/*` and plain `dir` workspace patterns we use. */
function resolveWorkspaceDirs(patterns) {
const dirs = [];
for (const pattern of patterns) {
if (pattern.endsWith('/*')) {
const parent = path.join(repoRoot, pattern.slice(0, -2));
if (!existsSync(parent)) {
continue;
}
for (const entry of readdirSync(parent, {withFileTypes: true})) {
if (entry.isDirectory()) {
dirs.push(path.join(parent, entry.name));
}
}
} else {
dirs.push(path.join(repoRoot, pattern));
}
}
return dirs;
}

const rootFile = path.join(repoRoot, 'package.json');
const rootPackage = readPackageJson(rootFile);

const packageFiles = [rootFile];
for (const dir of resolveWorkspaceDirs(rootPackage.workspaces ?? [])) {
const file = path.join(dir, 'package.json');
if (existsSync(file)) {
packageFiles.push(file);
}
}

const found = packageFiles.map((file) => {
const {name, version} = readPackageJson(file);
return {file: path.relative(repoRoot, file), name, version};
});

const versions = new Set(found.map((entry) => entry.version));

if (versions.size > 1) {
console.error('Package versions disagree:\n');
for (const {file, name, version} of found) {
console.error(` ${version.padEnd(12)} ${name} (${file})`);
}
console.error(
'\nEvery package.json in this repo must declare the same version — the ' +
'release is tagged from it. Bump them together, e.g.:\n' +
'\n npm version <version> --workspaces --include-workspace-root --no-git-tag-version\n',
);
process.exit(1);
}

console.log(`All ${found.length} package.json files agree on version ${[...versions][0]}.`);
Loading