build(audience): make @imtbl/audience publishable on npm#2838
Merged
ImmutableJeffrey merged 4 commits intomainfrom Apr 9, 2026
Merged
build(audience): make @imtbl/audience publishable on npm#2838ImmutableJeffrey merged 4 commits intomainfrom
ImmutableJeffrey merged 4 commits intomainfrom
Conversation
d549817 to
8cf0447
Compare
|
View your CI Pipeline Execution ↗ for commit 481813f
☁️ Nx Cloud last updated this comment at |
Makes @imtbl/audience installable from npm (unblocks SDK-66) and picks it up by the monorepo's publish workflow (unblocks the first publish). Changes to packages/audience/sdk only: - tsup.config.js: local config that extends the monorepo defaults and sets noExternal: [/^@imtbl\//] so runtime JS inlines audience-core and its transitive metrics dep. - rollup.dts.config.js + rollup-plugin-dts: post-typegen step that rolls up the generated declaration file with respectExternal so public types are self-contained. - scripts/prepack.mjs + scripts/postpack.mjs: strip 'workspace:*' deps from the published package.json before pack, restore afterwards so the developer working tree stays on workspace form. - package.json: add 'pack:root' script matching every other shipped @imtbl/* package so the root 'pack-npm-packages' recursive invocation actually packs @imtbl/audience. Without this the publish workflow silently skipped the package. - .eslintignore: exempt the new tsup.config.js / rollup.dts.config.js from project-based eslint (mirrors existing pattern for build-time configs). Verified locally: - dist/node/*.js, dist/node/*.cjs, dist/browser/*.js contain no bare @imtbl/* imports - dist/types/index.d.ts contains no @imtbl/* re-exports - Installing the produced tarball into a fresh /tmp directory via 'npm install' resolves both the runtime require and the types - 'pnpm --filter @imtbl/audience pack:root' produces imtbl-audience-0.0.0.tgz at the repo root — same location pattern as passport, checkout, etc. Refs: SDK-66, SDK-63 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Declare esbuild-plugin-replace and esbuild-plugins-node-modules-polyfill as explicit devDependencies (were phantom deps via root hoisting) - Remove @uniswap/swap-router-contracts from noExternal (copy-paste artifact from root tsup config — audience has no uniswap dependency) - Add *.prepack-backup to .gitignore (prevents accidental commit if pnpm pack fails between prepack and postpack) - Clean up leftover per-file .d.ts after rollup bundles them into dist/types/index.d.ts (reduces tarball size) - Use import attribute `with` instead of deprecated `assert` syntax (Node 22+ deprecation) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
499e04b to
f64a9a5
Compare
nattb8
reviewed
Apr 9, 2026
…pack Previously tsup.config.js used a broad regex (/^@imtbl\//) for noExternal while prepack.mjs used an explicit list ['@imtbl/audience-core', '@imtbl/metrics']. If a new @imtbl/* package became a direct dep, tsup would silently bundle it but prepack would leave workspace:* in the published package.json — breaking `npm install @imtbl/audience`. Extract the list to scripts/bundled-workspace-deps.mjs as a single source of truth. Both tsup.config.js and prepack.mjs import from it, so adding a new bundled workspace dep is a one-line change in one file. Addresses PR review comment from @nattb8. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
nattb8
approved these changes
Apr 9, 2026
rodrigo-fournier-immutable
approved these changes
Apr 9, 2026
ImmutableJeffrey
added a commit
that referenced
this pull request
Apr 9, 2026
PR #2838 shipped the build fixes that make @imtbl/audience installable as a standalone npm package. However, the first publish run after merge (workflow run 24182647672) was unable to actually publish the fixed artifact because of a pre-existing version collision: Skipped package "@imtbl/audience" because v0.0.1-alpha.0 already exists in https://registry.npmjs.org/ with tag "alpha" The collision stems from two root causes that compound: 1. Audience source versions were left at the 0.0.0 template value, so nx release's prerelease specifier always computes 0.0.1-alpha.0 for them — but that version number was burned on 2026-04-08 by an earlier publish attempt that pushed the pre-2838 broken build (no prepack stripping, @imtbl/audience-core still listed as a runtime dep pointing at a package that isn't published). The registry refuses to overwrite, so every subsequent publish run skips audience indefinitely. 2. The "Initialize current versions" step in publish.yaml seeds package versions from @imtbl/metrics for packages inside the @imtbl/sdk... and @imtbl/checkout-widgets... dependency trees. @imtbl/audience isn't in either tree, so it never gets seeded and drifts out of lockstep with the rest of the SDK family — which is what produces the 0.0.1-alpha.0 bump in the first place. PR #2838's body explicitly flagged this as unverified: "nx release *should* bump it to match the rest (since projectsRelationship is fixed) — but this hasn't been independently verified". It doesn't — without an explicit filter entry, nx reads the disk version (0.0.0) and bumps from there. This patch fixes both sides: - packages/audience/sdk/package.json and packages/audience/core/package.json: bump source version 0.0.0 → 2.15.0-alpha.19 (matching the current SDK-family source state). This alone unblocks the next publish — nx release will compute 2.15.0-alpha.20 on next run, which is a fresh version slot and publishes cleanly. - .github/workflows/publish.yaml: add --filter @imtbl/audience... to the Initialize current versions step. This seeds audience (and audience-core via the ... transitive filter) to the latest metrics version from npm on every publish run, keeping audience in lockstep with the rest of the SDK family long-term. Without this, the next publish after 2.15.0-alpha.20 lands would drift audience out of sync again. @imtbl/audience-core is private and bundled inline into @imtbl/audience via tsup noExternal (PR #2838), so its version never reaches consumers — but keeping it aligned with the SDK family avoids confusing diffs in the transient workspace:* resolution step. Refs: SDK-66, SDK-63
6 tasks
ImmutableJeffrey
added a commit
that referenced
this pull request
Apr 9, 2026
…g filter PR #2838 shipped the build fixes that make @imtbl/audience installable as a standalone npm package. However, the first publish run after merge (workflow run 24182647672) was unable to actually publish the fixed artifact because of a version collision: Skipped package "@imtbl/audience" because v0.0.1-alpha.0 already exists in https://registry.npmjs.org/ with tag "alpha" ## Root cause The "Initialize current versions" step in publish.yaml seeds package versions from @imtbl/metrics for packages inside the @imtbl/sdk... and @imtbl/checkout-widgets... dependency trees. Each SDK-family package has "version": "0.0.0" committed in source (as a template); the Initialize step is the source of truth at publish time. @imtbl/audience isn't in either of those dependency trees, so it never gets seeded. nx release reads the committed template value 0.0.0 from disk and computes 0.0.1-alpha.0 — which was burned on 2026-04-08 by an earlier publish attempt that pushed the pre-2838 broken build (no prepack stripping, @imtbl/audience-core still listed as a runtime dep pointing at a package that's never published since it's private). The registry refuses to overwrite, so every publish run skips audience indefinitely. Evidence the npm copy is still the broken pre-2838 artifact: $ npm view @imtbl/audience@0.0.1-alpha.0 dependencies --json {"@imtbl/audience-core": "0.0.1-alpha.0"} $ npm view @imtbl/audience@0.0.1-alpha.0 time.created 2026-04-08T04:43:20.487Z # one day before PR #2838 merged $ npm view @imtbl/audience-core versions E404 Not Found # private, never published $ cd /tmp/fresh && npm install @imtbl/audience@0.0.1-alpha.0 E404 on @imtbl/audience-core@0.0.1-alpha.0 PR #2838's body explicitly flagged this as unverified: > nx release should bump it to match the rest (since > projectsRelationship is fixed) — but this hasn't been independently > verified. Verified now: projectsRelationship: fixed does NOT make nx reconcile different starting disk versions. It just means all projects bump under the same release cycle with the same specifier. Without an explicit filter entry in the Initialize step, audience's disk 0.0.0 is read as-is and bumps to 0.0.1-alpha.0. ## Fix Add --filter @imtbl/audience... to the Initialize current versions step. This seeds @imtbl/audience AND @imtbl/audience-core (via the ... transitive-deps suffix) to the latest @imtbl/metrics version from npm on every publish run, matching the pattern used for every other SDK-family package. No source-version changes needed — audience follows the existing convention where source stays at 0.0.0 and the Initialize step is the canonical version writer. ## Expected behaviour on next publish 1. Initialize current versions: reads latest metrics version from npm (e.g. 2.15.0-alpha.20), writes it to all SDK-family package.json files including audience + audience-core. 2. Setup new package versions (nx release): bumps every seeded package from the metrics version to the next prerelease (e.g. 2.15.0-alpha.21) — audience/audience-core in lockstep with the rest of the SDK family. 3. Pack: prepack strips @imtbl/audience-core from audience's dependencies, postpack restores, producing imtbl-audience-<new>.tgz with no workspace references. 4. Release to NPM: publishes @imtbl/audience at the fresh version slot — no collision, because this slot has not been burned. 5. npm install @imtbl/audience@<new-version> in a fresh project resolves and installs cleanly. Refs: SDK-66, SDK-63
ImmutableJeffrey
added a commit
that referenced
this pull request
Apr 9, 2026
Addresses review feedback on #2837 from @nattb8: the interactive demo should live in its own workspace package (matching the repo convention used by passport/sdk-sample-app, checkout/sdk-sample-app, dex/sdk-sample-app, bridge/bridge-sample-app) rather than inside the published @imtbl/audience package directory. Why this matters beyond aesthetics: - @imtbl/audience is a published npm package with a dedicated build pipeline (#2838): local tsup.config.js, prepack/postpack scripts that strip workspace deps from package.json, rollup-plugin-dts to inline type re-exports. The sdk package directory should stay focused on shipping artifacts; a demo harness is not one. - The demo was vanilla ES2020 (no TS, no modules, loaded via a script tag) while the sdk package is pure TypeScript. Co-locating them forced sdk/.eslintignore + an .eslintrc.cjs override block just to keep lint-staged from trying to parse demo/*.js with the TS parser. Both pieces of config disappear with this move. - The existing repo-wide root .eslintignore already has a `**sample-app**/` glob (for passport/sdk-sample-app and friends), so the new directory is automatically excluded from root lint with zero local config. Addresses the reviewer's secondary concern — "this is included in the CDN bundle too" — at the structural level. For the record, verified the demo was never literally bundled into dist/cdn/imtbl-audience.global.js: src/cdn.ts imports only ./sdk, ./config, and @imtbl/audience-core, and `files: ["dist"]` in package.json already excluded demo/ from the npm tarball. Confirmed by packing the sdk and inspecting the tarball — it only contains dist/browser, dist/cdn, dist/node, dist/types, plus README.md, LICENSE.md, and package.json. Changes: New package — packages/audience/sdk-sample-app/ - package.json: private, @imtbl/audience as a workspace:* devDep, engines node >= 20.11, `pnpm dev` builds @imtbl/audience then runs the local serve script - serve.mjs: ~90-line Node static server using only the stdlib. Serves the sample-app's own files from ./, and routes /vendor/ to ../sdk/dist/cdn/ so the HTML can load the CDN bundle via a same-origin URL (keeps the demo's CSP happy). Blocks serve.mjs, package.json, and node_modules from being served, plus path traversal attempts via decodeURIComponent + a resolve/startsWith guard. Verified with curl: 200 for /, /demo.css, /demo.js and /vendor/imtbl-audience.global.js(.map); 403 for /package.json, /serve.mjs, /vendor/../../package.json, /%2e%2e/secret; 404 for /nonexistent.html. - index.html, demo.js, demo.css, README.md: git-renamed from packages/audience/sdk/demo/. The only content change is in index.html — the <script src> moved from ../dist/cdn/... to vendor/... — plus README.md was updated with the new run instructions and a layout diagram for the new location. Package cleanup — packages/audience/sdk/ - Remove the `demo` script from package.json (its entry point is gone now). - Revert .eslintrc.cjs to main's 6-line baseline by dropping the 22-line `demo/**/*.js` overrides block that the PR had added. - Delete .eslintignore entirely (its only line was `demo/`). - Update README.md's two `demo/` references to point at `../sdk-sample-app/README.md` instead. Repo-level - Drop the `packages/audience/sdk/demo/` line from root .eslintignore (the existing `**sample-app**/` glob covers the new location). - Register `packages/audience/sdk-sample-app` in pnpm-workspace.yaml. - pnpm-lock.yaml picks up a 6-line importer entry for the new package (just the workspace:* link to ../sdk, no external deps). Verification: - `pnpm --filter @imtbl/audience-core --filter @imtbl/audience run lint typecheck test` — 113 core + 51 sdk tests pass, lint/typecheck clean on both packages. - `pnpm --filter @imtbl/audience run build` — ESM (browser+node), CDN IIFE (52.04 KB), and rolled-up .d.ts all build clean. - `pnpm --filter @imtbl/audience-sdk-sample-app run dev` — builds the sdk, starts the local server, demo loads at http://localhost:3456/ with the CDN bundle served from /vendor/. - `pnpm pack --pack-destination /tmp/...` in the sdk — tarball contains only dist/{browser,cdn,node,types}, LICENSE.md, README.md, and package.json. No demo, no vendor, no sample-app, no scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ImmutableJeffrey
added a commit
that referenced
this pull request
Apr 9, 2026
Addresses review feedback on #2837 from @nattb8: the interactive demo should live in its own workspace package (matching the repo convention used by passport/sdk-sample-app, checkout/sdk-sample-app, dex/sdk-sample-app, bridge/bridge-sample-app) rather than inside the published @imtbl/audience package directory. Why this matters beyond aesthetics: - @imtbl/audience is a published npm package with a dedicated build pipeline (#2838): local tsup.config.js, prepack/postpack scripts that strip workspace deps from package.json, rollup-plugin-dts to inline type re-exports. The sdk package directory should stay focused on shipping artifacts; a demo harness is not one. - The demo was vanilla ES2020 (no TS, no modules, loaded via a script tag) while the sdk package is pure TypeScript. Co-locating them forced sdk/.eslintignore + an .eslintrc.cjs override block just to keep lint-staged from trying to parse demo/*.js with the TS parser. Both pieces of config disappear with this move. - The existing repo-wide root .eslintignore already has a `**sample-app**/` glob (for passport/sdk-sample-app and friends), so the new directory is automatically excluded from root lint with zero local config. Addresses the reviewer's secondary concern — "this is included in the CDN bundle too" — at the structural level. For the record, verified the demo was never literally bundled into dist/cdn/imtbl-audience.global.js: src/cdn.ts imports only ./sdk, ./config, and @imtbl/audience-core, and `files: ["dist"]` in package.json already excluded demo/ from the npm tarball. Confirmed by packing the sdk and inspecting the tarball — it only contains dist/browser, dist/cdn, dist/node, dist/types, plus README.md, LICENSE.md, and package.json. Changes: New package — packages/audience/sdk-sample-app/ - package.json: private, @imtbl/audience as a workspace:* devDep, engines node >= 20.11, `pnpm dev` builds @imtbl/audience then runs the local serve script - serve.mjs: ~90-line Node static server using only the stdlib. Serves the sample-app's own files from ./, and routes /vendor/ to ../sdk/dist/cdn/ so the HTML can load the CDN bundle via a same-origin URL (keeps the demo's CSP happy). Blocks serve.mjs, package.json, and node_modules from being served, plus path traversal attempts via decodeURIComponent + a resolve/startsWith guard. Verified with curl: 200 for /, /demo.css, /demo.js and /vendor/imtbl-audience.global.js(.map); 403 for /package.json, /serve.mjs, /vendor/../../package.json, /%2e%2e/secret; 404 for /nonexistent.html. - index.html, demo.js, demo.css, README.md: git-renamed from packages/audience/sdk/demo/. The only content change is in index.html — the <script src> moved from ../dist/cdn/... to vendor/... — plus README.md was updated with the new run instructions and a layout diagram for the new location. Package cleanup — packages/audience/sdk/ - Remove the `demo` script from package.json (its entry point is gone now). - Revert .eslintrc.cjs to main's 6-line baseline by dropping the 22-line `demo/**/*.js` overrides block that the PR had added. - Delete .eslintignore entirely (its only line was `demo/`). - Update README.md's two `demo/` references to point at `../sdk-sample-app/README.md` instead. Repo-level - Drop the `packages/audience/sdk/demo/` line from root .eslintignore (the existing `**sample-app**/` glob covers the new location). - Register `packages/audience/sdk-sample-app` in pnpm-workspace.yaml. - pnpm-lock.yaml picks up a 6-line importer entry for the new package (just the workspace:* link to ../sdk, no external deps). Verification: - `pnpm --filter @imtbl/audience-core --filter @imtbl/audience run lint typecheck test` — 113 core + 51 sdk tests pass, lint/typecheck clean on both packages. - `pnpm --filter @imtbl/audience run build` — ESM (browser+node), CDN IIFE (52.04 KB), and rolled-up .d.ts all build clean. - `pnpm --filter @imtbl/audience-sdk-sample-app run dev` — builds the sdk, starts the local server, demo loads at http://localhost:3456/ with the CDN bundle served from /vendor/. - `pnpm pack --pack-destination /tmp/...` in the sdk — tarball contains only dist/{browser,cdn,node,types}, LICENSE.md, README.md, and package.json. No demo, no vendor, no sample-app, no scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
@imtbl/audienceinstallable vianpm install @imtbl/audiencefrom the npm registry and includes it in the monorepo's publish workflow. Unblocks SDK-66 (Publish WebSDK on npm) and SDK-63 (integrate Web SDK into Play).The two problems this PR solves
1. The package wasn't installable outside the monorepo. The built output referenced
@imtbl/audience-coreas a dependency, but audience-core is a private internal package that's never published to npm. Any consumer runningnpm install @imtbl/audiencewould hit a missing dependency error.2. The publish workflow wasn't including audience at all.
.github/workflows/publish.yamlrunspnpm -r pack:rootto collect packages for publishing. Every other shipped@imtbl/*package defines apack:rootscript.packages/audience/sdkdid not, so the workflow silently skipped it.What the commits do
tsup.config.js— local build config that bundles all@imtbl/*workspace dependencies (audience-core + transitive metrics) directly into the output. This makes the package fully self-contained — no external@imtbl/*packages needed at runtime.rollup.dts.config.js+rollup-plugin-dts— bundles TypeScript type declarations into a single self-contained file. Without this, consumers would get unresolved type references to@imtbl/audience-core. Leftover per-file.d.tsfiles are cleaned up after bundling.scripts/prepack.mjs/scripts/postpack.mjs— removes@imtbl/audience-corefrompackage.jsonbefore packaging (since it's bundled inline and doesn't exist on npm), then restores it afterwards for local development. This pattern is unique to@imtbl/audiencebecause it's the only published package that depends on a private workspace package.package.json—pack:rootscript — registers audience with the publish workflow, matching every other shipped@imtbl/*package.package.json— explicit devDependencies —esbuild-plugin-replaceandesbuild-plugins-node-modules-polyfilldeclared explicitly rather than relying on root hoisting (matches@imtbl/checkout-sdkpattern)..eslintignore— exempts the new build configs from eslint..gitignore— adds*.prepack-backupto prevent accidental commit if packaging fails mid-way.Verified locally
@imtbl/*packages — fully self-contained@imtbl/*packagesnpm install ./imtbl-audience-0.0.0.tgz— both runtime and types resolve correctlypnpm --filter @imtbl/audience pack:rootproduces the package at the repo root on the first tryWhat this PR still does NOT do (flagged for verification before first publish)
Verify nx release assigns the correct version to @imtbl/audience. The publish workflow seeds versions from
@imtbl/metricsfor packages in@imtbl/sdk's dependency tree.@imtbl/audienceis not in that tree, so it starts at0.0.0. nx release should bump it to match the rest (sinceprojectsRelationshipisfixed) — but this hasn't been independently verified.Recommended first publish flow:
publish.yamlviaworkflow_dispatchwithdry_run: trueandrelease_type: prerelease@imtbl/audienceis versioned correctly and included in the package listdry_run: falsenpm install @imtbl/audience@<version>works from a fresh projectTickets
npm install @imtbl/audience; this PR makes that possibleTest plan
pnpm --filter @imtbl/audience build— ESM + CJS + browser + bundled types all producedpnpm --filter @imtbl/audience pack:root— package produced at repo root/tmpproject — runtime and types resolve@imtbl/*references in built outputworkflow_dispatch --dry_runto verify versioning and inclusion in publishRelationship to other open PRs
Independent of #2836 (foundation: errors + IdentityType) and #2837 (demo + CDN bundle). No file collisions; only adjacent-line overlap in
packages/audience/sdk/package.json. Git auto-merges; whichever lands second regeneratespnpm-lock.yaml.🤖 Generated with Claude Code