Summary
Every published @setapp/framework-wrapper npm package ships a prebuilt N-API binding compiled against an older Setapp library than the package it is in. 5.3.3 ships a binding built against 5.2.1.
Because the binding statically embeds the Swift library, the embedded version is the library that runs. So an Electron app on @setapp/framework-wrapper@5.3.3 runs Setapp library 5.2.1, and cannot use anything added in 5.3.0–5.3.3.
This is reproducible from your repository alone, with no third-party code involved.
Evidence
Each Mach-O the framework produces carries an embedded __scv__<version> stamp. Comparing the two binaries inside a single published package, pulled straight from npm:
| package |
Setapp.xcframework/…/libSetapp.a |
published N-API binding |
binding sha256 |
| 4.3.4 |
4.3.4 |
4.3.1 |
0fb32263502d… |
| 4.3.5 |
4.3.5 |
4.3.4 |
9685e5662d00… |
| 5.0.0 |
5.0.0 |
4.3.4 |
9685e5662d00… |
| 5.0.1 |
5.0.0 |
4.3.3 |
cd6084928c16… |
| 5.1.0 |
5.1.0 |
5.1.0 |
d79de25cd5b5… |
| 5.2.0 |
5.2.0 |
5.1.0 |
d79de25cd5b5… |
| 5.2.1 |
5.2.1 |
5.1.0 |
d79de25cd5b5… |
| 5.3.0 |
5.3.0 |
5.2.1 |
0526114f9eb1… |
| 5.3.1 |
5.3.1 |
5.2.1 |
0526114f9eb1… |
| 5.3.2 |
5.3.2 |
5.2.1 |
0526114f9eb1… |
| 5.3.3 |
5.3.3 |
5.2.1 |
0526114f9eb1… |
The binding has been byte-identical across 5.3.0 → 5.3.3. 5.1.0 appears to be the only release where it matched.
The libSetapp.a in the package cannot compensate, because the binding does not link it dynamically:
$ nm -gU node_setapp_binding.node | grep -ci setapp # 6525 (Swift symbols embedded)
$ otool -L node_setapp_binding.node | grep -ci libSetapp # 0 (no dynamic reference)
And only that one copy can ever load — main is ./nodejs/dist/lib/setapp.js, and nodejs/dist/lib/setapp-binding.js:10 does a plain relative require with no search path:
exports.binding = binding = require('./binding/node_setapp_binding.node');
Concrete consequence: 5.3.x APIs are missing
The 5.3.3 changelog says:
New — [SetappAI] Added a new credits API — setappAI.credits.balances().
Symbol counts, published binding vs one built from this repo at the 5.3.3 tag:
| symbol |
published binding |
built from source |
credits |
8 |
36 |
balances |
0 |
12 (incl. CreditBalancesResponse) |
The same applies to 5.3.0's text-to-video and 5.3.2's model context-window info. None of it is reachable from Electron.
Root cause
The publish chain never rebuilds the binding. From package.json:
"prebuild": "rimraf ./nodejs/dist",
"build": "tsc",
"postbuild": "mkdir ./nodejs/dist/lib/binding && cp ./nodejs/lib/binding/node_setapp_binding.node ./nodejs/dist/lib/binding/node_setapp_binding.node",
"prepublishOnly": "npm run build"
build is tsc only — it never invokes node-gyp. postbuild then copies whatever .node happens to be in nodejs/lib/binding/, and that directory is gitignored, so it is untracked build output on the publishing machine.
Running the chain on a clean checkout produces the correct binding:
git clone --depth 1 --branch 5.3.3 https://github.com/MacPaw/Setapp-framework.git
cd Setapp-framework && npm install && npm run build && npm pack
# dist binding: __scv__5.3.3 (published 5.3.3 tarball: __scv__5.2.1)
So the release process itself works. The artifact on npm was not produced by it on a clean tree.
The documented rebuild cannot work around it
Integration with an Electron app instructs:
"postinstall": "electron-builder install-app-deps"
That rebuild writes to nodejs/lib/binding, because binding.gyp declares both copy destinations as the same path:
"module_bindnings_path": "nodejs/lib/binding",
"path_for_nodejs_package": "nodejs/lib/binding",
main loads nodejs/dist/lib/binding. Only postbuild bridges them, and it runs from npm run build / prepublishOnly, never from npm install.
So the documented rebuild step is a no-op for the binary that actually loads. Following the guide exactly still ships the stale binding.
(Separately: that page still specifies "@setapp/framework-wrapper": "^3.1.2".)
Why we cannot just build it ourselves
We tried, and this is worth flagging because it may indicate a second issue.
A binding built from this repo at the 5.3.3 tag, via npm install && npm run build, carries the correct library — but a signed and notarized app using it does not complete the provisioning handshake. It shows:
This copy of LazyReel requires an active Setapp account installed on your Mac.
Verified on the same Mac, minutes apart, with SetappAgent -xpcServer running and Setapp client 3.54.0:
| binding in the app |
result |
published (__scv__5.2.1) |
works |
built from source (__scv__5.3.3) |
dialog above |
The failing build passed codesign --verify --deep --strict and spctl -a -t exec (accepted, Notarized Developer ID), so signing is not the explanation. Both binaries are platform 1, minos 12.0, differing only in SDK (26.0 vs 26.5).
We do not know why yours connects and ours does not — but it means integrators cannot self-serve a current library, so the fix has to come from the published package.
Suggested fix
Either make build rebuild the binding before postbuild copies it, or have postbuild fail when the copied binary's __scv__ stamp does not match the package version. The second is two lines and would have caught every row in the table above.
Environment
- macOS 26.5, Apple Silicon
- Electron 41, Node 24
@setapp/framework-wrapper 5.2.0 → 5.3.3 inspected; app shipping 5.3.3
Summary
Every published
@setapp/framework-wrappernpm package ships a prebuilt N-API binding compiled against an older Setapp library than the package it is in.5.3.3ships a binding built against5.2.1.Because the binding statically embeds the Swift library, the embedded version is the library that runs. So an Electron app on
@setapp/framework-wrapper@5.3.3runs Setapp library 5.2.1, and cannot use anything added in 5.3.0–5.3.3.This is reproducible from your repository alone, with no third-party code involved.
Evidence
Each Mach-O the framework produces carries an embedded
__scv__<version>stamp. Comparing the two binaries inside a single published package, pulled straight from npm:Setapp.xcframework/…/libSetapp.a0fb32263502d…9685e5662d00…9685e5662d00…cd6084928c16…d79de25cd5b5…d79de25cd5b5…d79de25cd5b5…0526114f9eb1…0526114f9eb1…0526114f9eb1…0526114f9eb1…The binding has been byte-identical across 5.3.0 → 5.3.3.
5.1.0appears to be the only release where it matched.The
libSetapp.ain the package cannot compensate, because the binding does not link it dynamically:And only that one copy can ever load —
mainis./nodejs/dist/lib/setapp.js, andnodejs/dist/lib/setapp-binding.js:10does a plain relative require with no search path:Concrete consequence: 5.3.x APIs are missing
The 5.3.3 changelog says:
Symbol counts, published binding vs one built from this repo at the
5.3.3tag:creditsbalancesCreditBalancesResponse)The same applies to 5.3.0's text-to-video and 5.3.2's model context-window info. None of it is reachable from Electron.
Root cause
The publish chain never rebuilds the binding. From
package.json:buildistsconly — it never invokes node-gyp.postbuildthen copies whatever.nodehappens to be innodejs/lib/binding/, and that directory is gitignored, so it is untracked build output on the publishing machine.Running the chain on a clean checkout produces the correct binding:
So the release process itself works. The artifact on npm was not produced by it on a clean tree.
The documented rebuild cannot work around it
Integration with an Electron app instructs:
That rebuild writes to
nodejs/lib/binding, becausebinding.gypdeclares both copy destinations as the same path:mainloadsnodejs/dist/lib/binding. Onlypostbuildbridges them, and it runs fromnpm run build/prepublishOnly, never fromnpm install.So the documented rebuild step is a no-op for the binary that actually loads. Following the guide exactly still ships the stale binding.
(Separately: that page still specifies
"@setapp/framework-wrapper": "^3.1.2".)Why we cannot just build it ourselves
We tried, and this is worth flagging because it may indicate a second issue.
A binding built from this repo at the
5.3.3tag, vianpm install && npm run build, carries the correct library — but a signed and notarized app using it does not complete the provisioning handshake. It shows:Verified on the same Mac, minutes apart, with
SetappAgent -xpcServerrunning and Setapp client 3.54.0:__scv__5.2.1)__scv__5.3.3)The failing build passed
codesign --verify --deep --strictandspctl -a -t exec(accepted, Notarized Developer ID), so signing is not the explanation. Both binaries areplatform 1,minos 12.0, differing only in SDK (26.0 vs 26.5).We do not know why yours connects and ours does not — but it means integrators cannot self-serve a current library, so the fix has to come from the published package.
Suggested fix
Either make
buildrebuild the binding beforepostbuildcopies it, or havepostbuildfail when the copied binary's__scv__stamp does not match the package version. The second is two lines and would have caught every row in the table above.Environment
@setapp/framework-wrapper5.2.0 → 5.3.3 inspected; app shipping 5.3.3