From 243e69620f051b83b35f153a1da33548fd6385b3 Mon Sep 17 00:00:00 2001 From: Kevin Miller <270536+kevee@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:07:19 -0400 Subject: [PATCH] chore: fix pinning to interface-alpha --- CHANGELOG.md | 6 +++ CLAUDE.md | 6 ++- examples/todo-list/package.json | 2 +- package-lock.json | 14 +++---- package.json | 2 +- packages/testing/README.md | 8 ++-- packages/testing/package.json | 2 +- packages/testing/src/index.ts | 2 +- packages/testing/src/sdk_internals.ts | 58 +++++++++++++++++++++------ 9 files changed, 73 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4596ad4..7189c53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.0] - 2026-08-01 + +### Changed + +- Pinned Airtable blocks to "interface-alpha" because that is what most consumers have. + ## [0.1.1] - 2026-07-30 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 622ff6b..55853d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,9 @@ npm workspaces monorepo: ## Known constraints -- Pinned/validated SDK build: `@airtable/blocks@interface-alpha-next` = `0.0.0-experimental-dc9f9a979-20260605`. A new experimental build can move internals; the library fails loudly at import if the dist files vanish. Update the README's validated-version note when bumping. +- Pinned/validated SDK build: `@airtable/blocks@interface-alpha` = `0.0.0-experimental-8575f0e0d-20260428` — the tag Airtable's own extension templates install, so it's what most consumers have. The suite also passes against `interface-alpha-next`. A new experimental build can move internals; the library fails loudly at import if the dist files vanish. Update the README's validated-version note when bumping. +- **The two builds differ in their `exports` map:** `interface-alpha-next` exports `./package.json`, `interface-alpha` does not. `resolveSdkRoot()` in `src/sdk_internals.ts` therefore tries `./package.json` first and falls back to resolving `@airtable/blocks/interface/ui` and walking up to the owning directory. Don't "simplify" it back to a single `require.resolve('@airtable/blocks/package.json')` — that breaks `interface-alpha` entirely. +- Changing the SDK dist-tag needs the lockfile entry dropped to take effect: npm won't re-resolve (or downgrade) a dist-tag spec on its own. Edit both workspaces' `package.json`, delete `packages['node_modules/@airtable/blocks']` from `package-lock.json`, then `npm install`. - The SDK's published ESM uses extensionless relative imports → **only runs under a transforming runner** (Jest+babel validated; Vitest plausible, unvalidated; plain Node impossible). Don't chase plain-Node support. - Consumers use the bundled Jest preset (`packages/testing/jest-preset.cjs`, exported as `./jest-preset`): jsdom + inject setupFile + hermetic babel transform allowed to transform the ESM-only SDK + jest-dom. The toolchain (jest, babel, testing-library) ships in the testing package's `dependencies`; react/react-dom/@airtable/blocks stay peers. The examples workspace proves the preset. The testing package's OWN jest config stays hand-rolled because its tests run against `src/` and need `babel-plugin-transform-import-meta`; consumers of the built dist don't. - Neither package is on npm. Distribution is GitHub Release tarballs, because npm cannot install a workspace subdirectory from a git URL and GitHub Packages would force every consumer to authenticate. Inside this repo the CLI bin resolves via `npx`; elsewhere, install the fixtures tarball. @@ -64,6 +66,8 @@ Security rules adapted from [TikiTribe/claude-secure-coding-rules](https://githu ## Work log +- **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. - **2026-07-23** — Added the release process: root `pack:release` script (build → mkdir → pack both packages into `release/`) and `.github/workflows/release.yml`, which on a `v*` tag verifies the tag matches both package versions, runs lint/build/test, packs, and publishes a GitHub Release with the tarballs attached. Consumer install instructions (release URLs and how to find them) added to the root README, both package READMEs, and docs/getting-started.md; maintainer release steps in the root README. - **2026-07-23** — Fixed the CI build failure: the root `build` script now builds the testing package before the fixture generator explicitly, instead of relying on `--workspaces` ordering (see Commands). Verification lesson: check build exit codes directly — piping the build through `grep -c "Build success"` masked the non-zero status and made a clean-clone check look green. diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index f5c9c02..9440604 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -9,7 +9,7 @@ "types": "tsc" }, "dependencies": { - "@airtable/blocks": "interface-alpha-next", + "@airtable/blocks": "interface-alpha", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/package-lock.json b/package-lock.json index aa3c145..fc6eb62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "airtable-interface-extension-testing", - "version": "0.0.0", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "airtable-interface-extension-testing", - "version": "0.0.0", + "version": "0.1.0", "workspaces": [ "packages/*", "examples/*" @@ -27,7 +27,7 @@ "name": "@usdr/example-todo-list", "version": "0.1.0", "dependencies": { - "@airtable/blocks": "interface-alpha-next", + "@airtable/blocks": "interface-alpha", "react": "^19.1.0", "react-dom": "^19.1.0" }, @@ -46,9 +46,9 @@ "license": "MIT" }, "node_modules/@airtable/blocks": { - "version": "0.0.0-experimental-dc9f9a979-20260605", - "resolved": "https://registry.npmjs.org/@airtable/blocks/-/blocks-0.0.0-experimental-dc9f9a979-20260605.tgz", - "integrity": "sha512-+HSE2pA9SNcxvV5nsceoZKYQbRsMB6heW13cSIbtCDMYmz7rsDDpSOru+YQqA1aFCkU9++pc0b8pqZ+qTZnq6g==", + "version": "0.0.0-experimental-8575f0e0d-20260428", + "resolved": "https://registry.npmjs.org/@airtable/blocks/-/blocks-0.0.0-experimental-8575f0e0d-20260428.tgz", + "integrity": "sha512-zkCqH9XxlaT/7xY2/QJeFu+g4Cpz1dOvQxB7GHlcpT+oQ7gUTFniHOPvnJDU1S9emeOtgKQ+q4dVfkgClXaGSQ==", "license": "UNLICENSED", "peerDependencies": { "react": "^19.0.0", @@ -9912,7 +9912,7 @@ "jest-environment-jsdom": "^29.7.0" }, "devDependencies": { - "@airtable/blocks": "interface-alpha-next", + "@airtable/blocks": "interface-alpha", "@types/jest": "^29.5.14", "@types/node": "^22.0.0", "@types/react": "^19.1.0", diff --git a/package.json b/package.json index 5128108..c09ffed 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "airtable-interface-extension-testing", "private": true, - "version": "0.1.0", + "version": "0.2.0", "description": "Testing library and fixture tooling for Airtable interface extensions (interface-alpha Blocks SDK)", "type": "module", "workspaces": [ diff --git a/packages/testing/README.md b/packages/testing/README.md index e47c41e..bdd0c9e 100644 --- a/packages/testing/README.md +++ b/packages/testing/README.md @@ -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-next +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 ``` **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-.tgz` under **Assets**. Every release follows the same URL shape, so bumping the version in the command above works too: @@ -38,7 +38,9 @@ https://github.com/usdigitalresponse/airtable-interface-extension-testing/releas The toolchain — **Jest**, jsdom, babel, and **Testing Library** — ships as this package's dependencies, so that one install is enough. **React 19** and `react-dom` are peer dependencies your extension project already has. -This library was validated against `@airtable/blocks@0.0.0-experimental-dc9f9a979-20260605` (the `interface-alpha-next` dist-tag as of July 2026). It reaches into the SDK's `dist/` for a few modules the package doesn't export publicly, so a new SDK build can break it — if that happens you'll get a descriptive error at import time rather than silent misbehavior. +This library is developed and tested against the `interface-alpha` dist-tag — the same build [Airtable's extension templates](https://github.com/Airtable/interface-extensions-hello-world-typescript) install — currently `0.0.0-experimental-8575f0e0d-20260428`. The suite also passes against `interface-alpha-next` (`0.0.0-experimental-dc9f9a979-20260605`), so either works. + +It reaches into the SDK's `dist/` for a few modules the package doesn't export publicly, so a new SDK build can break it — if that happens you'll get a descriptive error at import time rather than silent misbehavior. ## Jest configuration @@ -182,7 +184,7 @@ Record and globalConfig testing carry over almost unchanged — you drive writes **"attempted to communicate with the host application outside of a simulated environment" ---** your extension rendered (or called SDK functions) outside `testDriver.Container`. Wrap the render, and access state through the driver. -**`ERR_PACKAGE_PATH_NOT_EXPORTED` or missing-module errors mentioning `dist/esm` ---** the installed `@airtable/blocks` isn't an interface-alpha build. Install `@airtable/blocks@interface-alpha-next`. +**`ERR_PACKAGE_PATH_NOT_EXPORTED` or missing-module errors mentioning `dist/esm` ---** the installed `@airtable/blocks` isn't an interface-alpha build. Install `@airtable/blocks@interface-alpha`. **`SyntaxError: Cannot use import statement outside a module` pointing into `@airtable/blocks` ---** Jest isn't transforming the SDK; check `transformIgnorePatterns`. diff --git a/packages/testing/package.json b/packages/testing/package.json index b364082..3781a0a 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -49,7 +49,7 @@ "jest-environment-jsdom": "^29.7.0" }, "devDependencies": { - "@airtable/blocks": "interface-alpha-next", + "@airtable/blocks": "interface-alpha", "@types/jest": "^29.5.14", "@types/node": "^22.0.0", "@types/react": "^19.1.0", diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 63004ad..f0bcd28 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -19,7 +19,7 @@ for (const relativePath of Object.values(sdkInternalPaths)) { throw spawnError( 'The installed @airtable/blocks package (%s) is missing %s. This ' + 'testing library supports the interface-alpha builds of the SDK ' + - '(`npm install --save-dev @airtable/blocks@interface-alpha-next`).', + '(`npm install --save-dev @airtable/blocks@interface-alpha`).', sdkPackageRoot, relativePath, ); diff --git a/packages/testing/src/sdk_internals.ts b/packages/testing/src/sdk_internals.ts index 7f938b5..ce4b2f0 100644 --- a/packages/testing/src/sdk_internals.ts +++ b/packages/testing/src/sdk_internals.ts @@ -2,9 +2,9 @@ * Access to interface-mode SDK modules that ship in `@airtable/blocks`'s * `dist/` but are not listed in its `exports` map (only `./base/*`, * `./interface/models`, `./interface/ui`, and the base-mode testing utils - * are). We resolve the package root through `./package.json` (which IS - * exported) and load the files by absolute path, which bypasses the exports - * map in every resolver we care about. + * are). We locate the package root (see `resolveSdkRoot`) and load the files + * by absolute path, which bypasses the exports map in every resolver we care + * about. * * Module identity is preserved in both runtimes that matter: * - Under Jest (babel CJS transform), the ambient `require` is Jest's, so @@ -14,6 +14,7 @@ * - Under real Node ESM/CJS, `createRequire`'s require(esm) shares Node's ESM * module cache with `import` (Node >= 20.19). */ +import * as fs from 'node:fs'; import {createRequire} from 'node:module'; import * as path from 'node:path'; import {type ReactNode} from 'react'; @@ -34,16 +35,49 @@ const nodeRequire = createRequire( typeof __filename !== 'undefined' ? __filename : import.meta.url, ); -let sdkRoot: string; -try { - sdkRoot = path.dirname(nodeRequire.resolve('@airtable/blocks/package.json')); -} catch { - throw spawnError( - '@airtable/blocks could not be resolved. Install the interface-alpha ' + - 'build of the SDK, e.g. `npm install --save-dev @airtable/blocks@interface-alpha-next`.', - ); +/** + * Locate the installed SDK package directory. + * + * The `interface-alpha-next` build exports `./package.json`, which makes this + * a one-liner. The `interface-alpha` build does not, so fall back to an entry + * point both builds export and walk up to the directory that owns it. + */ +function resolveSdkRoot(): string { + try { + return path.dirname(nodeRequire.resolve('@airtable/blocks/package.json')); + } catch { + // Falls through to the entry-point strategy below. + } + + let entryPath: string; + try { + entryPath = nodeRequire.resolve('@airtable/blocks/interface/ui'); + } catch { + throw spawnError( + '@airtable/blocks could not be resolved. Install an interface-alpha ' + + 'build of the SDK, e.g. `npm install --save-dev @airtable/blocks@interface-alpha`.', + ); + } + + // e.g. /dist/esm/interface/ui/ui.js -> + for (let dir = path.dirname(entryPath); ; ) { + if (fs.existsSync(path.join(dir, 'package.json'))) { + return dir; + } + const parent = path.dirname(dir); + /* istanbul ignore next -- only reachable if the install is corrupt */ + if (parent === dir) { + throw spawnError( + 'Could not locate the @airtable/blocks package directory from %s.', + entryPath, + ); + } + dir = parent; + } } +const sdkRoot = resolveSdkRoot(); + function loadSdkModule(relativePath: string): any { const absolutePath = path.join(sdkRoot, relativePath); try { @@ -62,7 +96,7 @@ function loadSdkModule(relativePath: string): any { throw spawnError( 'Failed to load %s from the installed @airtable/blocks package (%s). ' + 'This testing library requires the interface-alpha build of the SDK ' + - '(`npm install --save-dev @airtable/blocks@interface-alpha-next`). ' + + '(`npm install --save-dev @airtable/blocks@interface-alpha`). ' + 'Original error: %s', relativePath, sdkRoot,