Skip to content

Commit 40862a1

Browse files
committed
Fix OpenTUI native integrity lockfile lookup
bun.lock nests native package version pins under @opentui/core optionalDependencies on the same line as core's sha512. Matching the package name alone selected that wrong hash and broke cross-compile fetches. Require the packages-array entry shape so integrity checks the native tarball hash. Fixes CL-7117
1 parent 11cc842 commit 40862a1

4 files changed

Lines changed: 107 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
1818
- Direct macOS release binaries are now signed with hardened-runtime Bun
1919
entitlements and notarized for both architectures. Publication fails closed
2020
unless the final tarballs pass signature, signer, Team ID, entitlement,
21-
architecture, and Gatekeeper validation.
21+
architecture, and Gatekeeper validation, the release Mac completes a
22+
host-native signed OpenTUI smoke, and cross-compiled OpenTUI native packages
23+
match their bun.lock packages-array integrity before unpack.
2224

2325
## [0.3.7] - 2026-08-27
2426

docs/RELEASING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ update the Homebrew tap unless both macOS binaries are freshly built, signed,
66
host-native OpenTUI-smoked on the release Mac, notarized, and validated from their
77
final tarballs. Cross-compiled opposite-arch macOS binaries still require signature,
88
notarization, and final-tarball verification, but they are never counted as
9-
host-native smoke.
9+
host-native smoke. Cross-compile OpenTUI native package fetches must match the
10+
bun.lock packages-array integrity hash before unpack; nested optionalDependencies
11+
pins are ignored for that check.
1012

1113
## Apple provisioning
1214

scripts/fetch-opentui-native.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ for tool in curl openssl tar awk mkdir rm; do
2828
command -v "$tool" >/dev/null 2>&1 || fail "missing tool: $tool"
2929
done
3030

31-
integrity=$(awk -v key="\"@opentui/${pkg}\":" '
31+
# Match the packages-array entry ("@opentui/pkg": [ ... "sha512-..." ]), not a
32+
# nested optionalDependencies version pin that shares the same package name on
33+
# @opentui/core's line and would otherwise yield core's integrity hash.
34+
integrity=$(awk -v key="\"@opentui/${pkg}\": [" '
3235
index($0, key) && match($0, /"sha512-[^"]+"/) {
3336
print substr($0, RSTART + 1, RLENGTH - 2)
3437
exit

tests/unit/release-native-validation.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,101 @@ cp "${tarball}" "$out"
151151
expect(result.stderr.toString()).toMatch(/integrity|checksum|sha512/i);
152152
expect(await Bun.file(join(dest, "package.json")).exists()).toBe(false);
153153
});
154+
155+
test("packages-array hash wins over nested optionalDependencies false match", async () => {
156+
const { directory, binDirectory } = await createStubBinDirectory();
157+
const lockfile = join(directory, "bun.lock");
158+
const dest = join(directory, "node_modules/@opentui/core-darwin-arm64");
159+
const packageRoot = join(directory, "package");
160+
const tarball = join(directory, "core-darwin-arm64-0.5.1.tgz");
161+
await mkdir(packageRoot);
162+
await writeFile(
163+
join(packageRoot, "package.json"),
164+
JSON.stringify({ name: "@opentui/core-darwin-arm64", version: "0.5.1" }),
165+
);
166+
const packed = Bun.spawnSync({
167+
cmd: ["tar", "-czf", tarball, "-C", directory, "package"],
168+
cwd: directory,
169+
stdout: "pipe",
170+
stderr: "pipe",
171+
});
172+
expect(packed.exitCode).toBe(0);
173+
174+
const digest = Bun.spawnSync({
175+
cmd: ["openssl", "dgst", "-sha512", "-binary", tarball],
176+
stdout: "pipe",
177+
stderr: "pipe",
178+
});
179+
expect(digest.exitCode).toBe(0);
180+
const packagesHash = `sha512-${Buffer.from(digest.stdout).toString("base64")}`;
181+
const coreFalseMatchHash =
182+
"sha512-mIBFyqIP4rkhQ35uldLXWawWQ6S9tvNWvmxGmDJ7W9cLXjegG6gKEfZ/4NyIMma755ERs/sqO/pIh3Ytf3DDFg==";
183+
expect(packagesHash).not.toBe(coreFalseMatchHash);
184+
185+
await writeFile(
186+
lockfile,
187+
`{
188+
"packages": {
189+
"@opentui/core": ["@opentui/core@0.5.1", "", { "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.1", "@opentui/core-darwin-x64": "0.5.1" } }, "${coreFalseMatchHash}"],
190+
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.1", "", { "os": "darwin", "cpu": "arm64" }, "${packagesHash}"],
191+
}
192+
}
193+
`,
194+
);
195+
await writeFile(
196+
join(binDirectory, "curl"),
197+
`#!/bin/sh
198+
set -eu
199+
out=""
200+
prev=""
201+
for arg in "$@"; do
202+
if [ "$prev" = "-o" ]; then out="$arg"; fi
203+
prev="$arg"
204+
done
205+
[ -n "$out" ]
206+
cp "${tarball}" "$out"
207+
`,
208+
);
209+
await chmod(join(binDirectory, "curl"), 0o755);
210+
211+
const accept = Bun.spawnSync({
212+
cmd: ["bash", fetchOpentui, "core-darwin-arm64", "0.5.1", dest, lockfile],
213+
cwd: root,
214+
env: {
215+
...process.env,
216+
PATH: `${binDirectory}:${process.env.PATH ?? ""}`,
217+
},
218+
stdout: "pipe",
219+
stderr: "pipe",
220+
});
221+
expect(accept.exitCode).toBe(0);
222+
expect(await Bun.file(join(dest, "package.json")).exists()).toBe(true);
223+
224+
await rm(dest, { recursive: true, force: true });
225+
const wrongPackagesHash =
226+
"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
227+
await writeFile(
228+
lockfile,
229+
`{
230+
"packages": {
231+
"@opentui/core": ["@opentui/core@0.5.1", "", { "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.1" } }, "${packagesHash}"],
232+
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.1", "", { "os": "darwin", "cpu": "arm64" }, "${wrongPackagesHash}"],
233+
}
234+
}
235+
`,
236+
);
237+
const reject = Bun.spawnSync({
238+
cmd: ["bash", fetchOpentui, "core-darwin-arm64", "0.5.1", dest, lockfile],
239+
cwd: root,
240+
env: {
241+
...process.env,
242+
PATH: `${binDirectory}:${process.env.PATH ?? ""}`,
243+
},
244+
stdout: "pipe",
245+
stderr: "pipe",
246+
});
247+
expect(reject.exitCode).not.toBe(0);
248+
expect(reject.stderr.toString()).toMatch(/integrity|checksum|sha512/i);
249+
expect(await Bun.file(join(dest, "package.json")).exists()).toBe(false);
250+
});
154251
});

0 commit comments

Comments
 (0)