Skip to content

Commit 0b8de82

Browse files
committed
chore: dump to version v1.0.22
1 parent 8b5135b commit 0b8de82

6 files changed

Lines changed: 182 additions & 40 deletions

File tree

branding/mac/dao.entitlements

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,19 @@
4444
<true/>
4545

4646
<!-- Touch ID / platform authenticator (WebAuthn passkeys). The Chromium
47-
Touch ID context stores credentials in a keychain access group derived
48-
at runtime as <TeamID>.<CFBundleIdentifier>.webauthn; without this
49-
entitlement it logs "Touch ID authenticator unavailable" on every
50-
WebAuthn capability probe and falls back to other authenticators.
51-
codesign substitutes $(AppIdentifierPrefix) -> "<TeamID>." and
52-
$(CFBundleIdentifier) from the bundle's Info.plist at sign time, so
53-
this works for both release (com.msgbyte.dao) and debug-suffixed
54-
(com.msgbyte.dao.debug) builds with a real Developer ID signature. -->
55-
<key>keychain-access-group</key>
47+
Touch ID context stores credentials in a keychain access group of the
48+
form <TeamID>.<CFBundleIdentifier>.webauthn; without this entitlement
49+
it logs "Touch ID authenticator unavailable" on every WebAuthn
50+
capability probe and falls back to other authenticators.
51+
The values below use Xcode-style placeholders for AppIdentifierPrefix
52+
and CFBundleIdentifier. macOS codesign does NOT expand them;
53+
package.ts renders this template into a tmp file (substituting the
54+
real Team ID and the debug-vs-release bundle id) before passing it
55+
to `codesign --entitlements`. Leaving an unexpanded placeholder in
56+
the signed entitlements makes taskgated reject the signature with
57+
"Code Signature Invalid" at launch — package.ts has a self-check
58+
that fails the build if any placeholder slips through. -->
59+
<key>keychain-access-groups</key>
5660
<array>
5761
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier).webauthn</string>
5862
</array>

dao.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": {
55
"product": "chromium",
66
"version": "147.0.7727.135",
7-
"display": "1.0.21"
7+
"display": "1.0.22"
88
},
99
"build": {
1010
"target_os": "mac",

scripts/commands/package.ts

Lines changed: 126 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
openSync,
1313
readSync,
1414
closeSync,
15+
readFileSync,
16+
writeFileSync,
1517
} from "node:fs";
1618
import os from "node:os";
1719
import path from "node:path";
@@ -382,18 +384,116 @@ function signAppBundle(appBundle: string, identity: string): void {
382384

383385
log(`Signing ${path.basename(appBundle)} with identity: ${identity}`);
384386

385-
const items = collectSignables(appBundle);
386-
// Sort by depth descending so children sign before parents.
387-
items.sort((a, b) => depth(b) - depth(a));
387+
// codesign does not expand $(AppIdentifierPrefix) / $(CFBundleIdentifier)
388+
// — those are Xcode build-phase substitutions. Render the templates here
389+
// so the signed entitlements contain fully-qualified literals; an
390+
// unexpanded placeholder would make taskgated reject the signature with
391+
// "Code Signature Invalid" at launch.
392+
const teamId = extractTeamId(identity);
393+
const bundleId = readBundleIdentifier(appBundle);
394+
const renderDir = mkdtempSync(path.join(os.tmpdir(), "dao-entitlements-"));
395+
const mainRendered = renderEntitlements(
396+
ENTITLEMENTS,
397+
renderDir,
398+
"dao.entitlements",
399+
teamId,
400+
bundleId
401+
);
402+
const helperRendered = renderEntitlements(
403+
HELPER_ENTITLEMENTS,
404+
renderDir,
405+
"dao_helper.entitlements",
406+
teamId,
407+
bundleId
408+
);
388409

389-
for (const item of items) {
390-
const ent = isAppBundle(item) ? HELPER_ENTITLEMENTS : null;
391-
signOne(item, identity, ent);
410+
try {
411+
const items = collectSignables(appBundle);
412+
// Sort by depth descending so children sign before parents.
413+
items.sort((a, b) => depth(b) - depth(a));
414+
415+
for (const item of items) {
416+
const ent = isAppBundle(item) ? helperRendered : null;
417+
signOne(item, identity, ent);
418+
}
419+
420+
// Outer app last.
421+
signOne(appBundle, identity, mainRendered);
422+
success("App bundle signed");
423+
} finally {
424+
rmSync(renderDir, { recursive: true, force: true });
392425
}
426+
}
393427

394-
// Outer app last.
395-
signOne(appBundle, identity, ENTITLEMENTS);
396-
success("App bundle signed");
428+
// Pull the 10-char Team ID out of "Developer ID Application: Foo Bar (TEAMID1234)".
429+
function extractTeamId(identity: string): string {
430+
const m = identity.match(/\(([A-Z0-9]{10})\)\s*$/);
431+
if (!m) {
432+
error(
433+
`Could not extract Team ID from DAO_SIGN_IDENTITY: "${identity}".\n` +
434+
' Expected format: "Developer ID Application: <Name> (TEAMID1234)"'
435+
);
436+
process.exit(1);
437+
}
438+
return m[1];
439+
}
440+
441+
function readBundleIdentifier(appBundle: string): string {
442+
const infoPlist = path.join(appBundle, "Contents", "Info.plist");
443+
if (!existsSync(infoPlist)) {
444+
error(`Info.plist not found inside app bundle: ${infoPlist}`);
445+
process.exit(1);
446+
}
447+
// `defaults read` strips the .plist suffix and prints the raw value.
448+
const out = run(
449+
`defaults read "${infoPlist.replace(/\.plist$/, "")}" CFBundleIdentifier`,
450+
{ silent: true }
451+
).trim();
452+
if (!out) {
453+
error(`CFBundleIdentifier is empty in ${infoPlist}`);
454+
process.exit(1);
455+
}
456+
return out;
457+
}
458+
459+
// Render an entitlements template by substituting Xcode-style placeholders,
460+
// then assert no `$(...)` remain. Any survivor would be silently signed into
461+
// the binary and rejected at launch by taskgated.
462+
//
463+
// Also strips XML comments before writing: codesign's AMFI parser is stricter
464+
// than plutil and rejects some perfectly valid UTF-8 inside <!-- ... -->
465+
// (em dashes, backticks, etc.) with "AMFIUnserializeXML: syntax error".
466+
// Comments are only useful for humans reading the source template anyway —
467+
// no need to ship them into the signature.
468+
function renderEntitlements(
469+
templatePath: string,
470+
outDir: string,
471+
outName: string,
472+
teamId: string,
473+
bundleId: string
474+
): string {
475+
const src = readFileSync(templatePath, "utf8");
476+
const stripped = src.replace(/<!--[\s\S]*?-->/g, "");
477+
const rendered = stripped
478+
// $(AppIdentifierPrefix) expands to "<TeamID>." (trailing dot — that's
479+
// how Xcode's expansion works, since downstream values concatenate
480+
// "$(AppIdentifierPrefix)$(CFBundleIdentifier)" without a separator).
481+
.replace(/\$\(AppIdentifierPrefix\)/g, `${teamId}.`)
482+
.replace(/\$\(CFBundleIdentifier\)/g, bundleId);
483+
484+
const leftover = rendered.match(/\$\([^)]+\)/);
485+
if (leftover) {
486+
error(
487+
`Unexpanded placeholder ${leftover[0]} in ${path.basename(templatePath)}.\n` +
488+
" Add it to renderEntitlements() in scripts/commands/package.ts,\n" +
489+
" or replace it with a literal value in the entitlements file."
490+
);
491+
process.exit(1);
492+
}
493+
494+
const outPath = path.join(outDir, outName);
495+
writeFileSync(outPath, rendered);
496+
return outPath;
397497
}
398498

399499
function collectSignables(root: string): string[] {
@@ -510,6 +610,23 @@ function verifyCodesign(appBundle: string): void {
510610
run(`codesign --verify --strict --deep --verbose=2 "${appBundle}"`, {
511611
silent: false,
512612
});
613+
// Last-line-of-defence: inspect the entitlements that actually got signed
614+
// into the bundle. Any `$(...)` survivor here means a placeholder slipped
615+
// past renderEntitlements, and taskgated will SIGKILL the app at launch
616+
// with "Code Signature Invalid".
617+
const embedded = run(
618+
`codesign -d --entitlements - --xml "${appBundle}"`,
619+
{ silent: true }
620+
);
621+
const leftover = embedded.match(/\$\([^)]+\)/);
622+
if (leftover) {
623+
error(
624+
`Signed entitlements still contain unexpanded ${leftover[0]}.\n` +
625+
" This will cause taskgated to reject the signature at launch.\n" +
626+
" Fix: handle the placeholder in renderEntitlements()."
627+
);
628+
process.exit(1);
629+
}
513630
success("Signature verified");
514631
}
515632

scripts/commands/release.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ interface ReleaseOptions {
2626
skipUpload?: boolean;
2727
skipBuild?: boolean;
2828
skipBump?: boolean;
29+
// Default true. Set to false via --no-force-import to call
30+
// `cli.ts import` without --force, e.g. when iterating on a release
31+
// failure and you've already reset engine/src yourself.
32+
forceImport?: boolean;
2933
// Resume from staple: dmg has already been notarized externally
3034
// (e.g. you ran `xcrun stapler staple dist/...dmg` yourself), and
3135
// we just need to finish appcast + copy + upload.
@@ -69,6 +73,12 @@ export const releaseCommand = new Command("release")
6973
.option("-p, --prefix <prefix>", "R2 key prefix for the .dmg", "")
7074
.option("--skip-upload", "Skip the R2 upload step (still produces artifacts)")
7175
.option("--skip-build", "Skip import + build (use existing dist/ artifact)")
76+
.option(
77+
"--no-force-import",
78+
"Run `cli.ts import` without --force (default: forced). " +
79+
"Use this when engine/src is already in a known-good state and you " +
80+
"don't want the import step to reset local edits."
81+
)
7282
.option(
7383
"--skip-bump",
7484
"Use the version already in dao.json instead of bumping " +
@@ -280,11 +290,14 @@ export const releaseCommand = new Command("release")
280290
// ------------------------------------------------------------------
281291
const skipBuildPhase = opts.skipBuild || opts.resumeFromStaple;
282292
if (!skipBuildPhase) {
293+
const forceImport = opts.forceImport !== false;
294+
const importArgs = ["tsx", "scripts/cli.ts", "import"];
295+
if (forceImport) importArgs.push("--force");
283296
await runStep(
284297
opts.dryRun,
285-
"Importing patches (force)",
298+
forceImport ? "Importing patches (force)" : "Importing patches",
286299
"npx",
287-
["tsx", "scripts/cli.ts", "import", "--force"]
300+
importArgs
288301
);
289302

290303
await runStep(opts.dryRun, "Building (release)", "npx", [

website/public/appcast.xml

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@
5454
type="application/octet-stream" />
5555
</item>
5656
-->
57+
<item>
58+
<title>1.0.22.0</title>
59+
<pubDate>Sun, 24 May 2026 18:04:37 +0800</pubDate>
60+
<sparkle:version>22.0</sparkle:version>
61+
<sparkle:shortVersionString>1.0.22.0</sparkle:shortVersionString>
62+
<sparkle:minimumSystemVersion>12.0</sparkle:minimumSystemVersion>
63+
<enclosure url="https://dao-release.msgbyte.com/dao-browser-1.0.22-mac-arm64.dmg" length="153929567" type="application/octet-stream" sparkle:edSignature="vcpeDS9KEI3m6vDXiVv7pZABiZ+NhUG9IujhwJocEs5BW7TW8CMu9a3fZWlefuDG96j5TdohHQmc95vJNz9UCw=="/>
64+
<sparkle:deltas>
65+
<enclosure url="https://dao-release.msgbyte.com/Dao22.0-21.0.delta" sparkle:deltaFrom="21.0" length="66490" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="HnoxxaFeMP8K7clBagGWV+YcYyXqHjQxoEXpvN0j7bU1+OWHcWWQBQFL8YvenJwEu53i1ETxQ4OP5xecUv65DA=="/>
66+
<enclosure url="https://dao-release.msgbyte.com/Dao22.0-20.0.delta" sparkle:deltaFrom="20.0" length="2731798" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="3hPRcyyZBPL0UPzWtdDgotpmMMeLFCnP844NffLGNn9babkzg5ZuM893WCXU8iGGnsHLQRF90TtEq5h+A0+uBg=="/>
67+
<enclosure url="https://dao-release.msgbyte.com/Dao22.0-19.0.delta" sparkle:deltaFrom="19.0" length="7300194" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="YFcyfa5i8XMw5UWqWuY9lmaJf7eCAe01UYGhpXzsRkjQA8RtCiildC4u/1BkwLrpwY+a8M0o1RxuiZqXNJL9CQ=="/>
68+
<enclosure url="https://dao-release.msgbyte.com/Dao22.0-17.0.delta" sparkle:deltaFrom="17.0" length="7298986" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="Ce5hTEFrVWINdPSVMxe7owVC69NaNExcwrL1RK97dgzcfTEQD0BhFSxKlu9sVljiOjUkS5GzLrlfe70rYkvMAQ=="/>
69+
<enclosure url="https://dao-release.msgbyte.com/Dao22.0-16.0.delta" sparkle:deltaFrom="16.0" length="7581166" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="047uNWAZ2kFqyhorZNEWl5VJser03vnPGEXAVqzquM/Cd9JIFsRAkq6VwF0NdIYDWlIOQzC7QYZIeGV6trjLAg=="/>
70+
</sparkle:deltas>
71+
<dao:gitCommit>55383d1d020efb05836a8d0622487d0a8ca3f8bc</dao:gitCommit>
72+
</item>
73+
<item>
74+
<title>1.0.21.0</title>
75+
<pubDate>Sat, 23 May 2026 10:46:05 +0800</pubDate>
76+
<sparkle:version>21.0</sparkle:version>
77+
<sparkle:shortVersionString>1.0.21.0</sparkle:shortVersionString>
78+
<sparkle:minimumSystemVersion>12.0</sparkle:minimumSystemVersion>
79+
<enclosure url="https://dao-release.msgbyte.com/dao-browser-1.0.21-mac-arm64.dmg" length="153930494" type="application/octet-stream" sparkle:edSignature="dQppEhrj/nb9wtrGXkS5ABFpLeG+nUKTqeouc/Jb6LTRhmyJFpAR/UfedEWLEiB1OMFX/S1uJaGh4qy8UZ3dAQ=="/>
80+
</item>
5781
<item>
5882
<title>1.0.20.0</title>
5983
<pubDate>Fri, 22 May 2026 04:27:46 +0800</pubDate>
@@ -70,21 +94,5 @@
7094
</sparkle:deltas>
7195
<dao:gitCommit>3a0e6fa5ed3c5cf4015a77d0f00436ef0b26875a</dao:gitCommit>
7296
</item>
73-
<item>
74-
<title>1.0.19.0</title>
75-
<pubDate>Thu, 21 May 2026 03:07:32 +0800</pubDate>
76-
<sparkle:version>19.0</sparkle:version>
77-
<sparkle:shortVersionString>1.0.19.0</sparkle:shortVersionString>
78-
<sparkle:minimumSystemVersion>12.0</sparkle:minimumSystemVersion>
79-
<enclosure url="https://dao-release.msgbyte.com/dao-browser-1.0.19-mac-arm64.dmg" length="154004609" type="application/octet-stream" sparkle:edSignature="rgw8FR84MmDe305sFRqnkY6zAVi927Au667Gp8StfygzwUT4cDsxKM1k4Tq1WfzXB13x915ngP+HvUqbkE6pCw=="/>
80-
<sparkle:deltas>
81-
<enclosure url="https://dao-release.msgbyte.com/Dao19.0-17.0.delta" sparkle:deltaFrom="17.0" length="2820974" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="6cmzta4v8pm5ZbJ23IT6IZI5MRj/gImBMU9awhep1x12rYD2O/jSc4VMizK84GTutAHZplJdOdMlA5wuGNtzCQ=="/>
82-
<enclosure url="https://dao-release.msgbyte.com/Dao19.0-16.0.delta" sparkle:deltaFrom="16.0" length="6413758" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="ZHlgm1tF2Os/4LMZHFZ8Y8jvu3tDBqvmnrtI8xWZcagKQfej+5qhmsj4hs+VmPgnpiQ8d1iOpIOB9nkaS7dXDg=="/>
83-
<enclosure url="https://dao-release.msgbyte.com/Dao19.0-15.0.delta" sparkle:deltaFrom="15.0" length="6746018" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="Lm7E+6MTD2aDIpPyUjYKtrY27Iv6EwFvtXUASgA668LANTC5t+UQebpGCAeY+PLrfn+M/9u+w1SemczBg3l/DQ=="/>
84-
<enclosure url="https://dao-release.msgbyte.com/Dao19.0-14.0.delta" sparkle:deltaFrom="14.0" length="6897794" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="zmX6IQ0MIbrHMpJiRQuwlkgmC2BTZy0EZ/3S5WO6f/f7wJDAWRWaGdHUTC5wkNmHvJjYkM90juOumscbl48gDQ=="/>
85-
<enclosure url="https://dao-release.msgbyte.com/Dao19.0-13.0.delta" sparkle:deltaFrom="13.0" length="6955546" type="application/octet-stream" sparkle:deltaFromSparkleExecutableSize="862416" sparkle:deltaFromSparkleLocales="de,he,ar,el,ja,fa,en" sparkle:edSignature="vKX5jtqh8ElNgGboSclY5kIi0WFsRs5tW0HOCuNUGeD1j9QVbTam2h1JEKwM2XY8bJEKwi6JpFIuick2uVKGCA=="/>
86-
</sparkle:deltas>
87-
<dao:gitCommit>613286101c73e8d3a6618631f6d61d7f18aaf748</dao:gitCommit>
88-
</item>
8997
</channel>
9098
</rss>

website/public/info.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"$schema": "Update this file to publish a new release. The /download route reads this at runtime and redirects to the platform-specific URL. Static-export-friendly (no server needed).",
3-
"version": "1.0.20",
3+
"version": "1.0.22",
44
"chromiumVersion": "147.0.7727.135",
5-
"releasedAt": "2026-05-23",
5+
"releasedAt": "2026-05-24",
66
"platforms": {
77
"macArm64": {
88
"label": "macOS (Apple Silicon)",
9-
"url": "https://dao-release.msgbyte.com/dao-browser-1.0.20-mac-arm64.dmg"
9+
"url": "https://dao-release.msgbyte.com/dao-browser-1.0.22-mac-arm64.dmg"
1010
}
1111
},
1212
"default": "macArm64"

0 commit comments

Comments
 (0)