Skip to content

Commit 30ae8c3

Browse files
committed
chore: update release script and rollback version
1 parent 0b8de82 commit 30ae8c3

4 files changed

Lines changed: 97 additions & 59 deletions

File tree

branding/mac/dao.entitlements

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,18 @@
4343
<key>com.apple.security.print</key>
4444
<true/>
4545

46-
<!-- Touch ID / platform authenticator (WebAuthn passkeys). The Chromium
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>
60-
<array>
61-
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier).webauthn</string>
62-
</array>
46+
<!-- NOTE: keychain-access-groups is a "restricted entitlement". macOS
47+
AMFI only honours it when the signature is backed by an Apple-issued
48+
provisioning profile (App Store, or a Developer ID build with an
49+
embedded .provisionprofile). Our Developer ID direct-sign pipeline
50+
ships no profile, so declaring keychain-access-groups makes AMFI
51+
abort launch with "Restricted entitlements not validated, error
52+
-67671 / -413 No matching profile found" — observed in 1.0.22.
53+
Touch ID / platform authenticator therefore stays in fallback mode
54+
(same as <=1.0.20). Re-enabling Touch ID requires registering the
55+
App ID + keychain access group on developer.apple.com, generating
56+
a Developer ID provisioning profile for the bundle id, embedding
57+
it at Dao.app/Contents/embedded.provisionprofile during packaging,
58+
and only then adding the entitlement back. -->
6359
</dict>
6460
</plist>

scripts/commands/release.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
existsSync,
66
readFileSync,
77
readdirSync,
8+
statSync,
89
writeFileSync,
910
} from "node:fs";
1011
import path from "node:path";
@@ -355,6 +356,20 @@ export const releaseCommand = new Command("release")
355356
process.exit(1);
356357
}
357358

359+
// ------------------------------------------------------------------
360+
// Snapshot pre-existing .delta files in dist/ BEFORE generate_appcast
361+
// runs, so we can distinguish "delta this run produced (or refreshed)"
362+
// from "delta that's been sitting in dist/ since a previous release".
363+
// Only the former needs to ship to R2 — the latter is already up there.
364+
// Captures filename → mtime; we compare both presence and mtime after
365+
// regen, because generate_appcast may overwrite an existing .delta if
366+
// the source dmg changed.
367+
// ------------------------------------------------------------------
368+
const distDir = path.join(ROOT_DIR, "dist");
369+
const preExistingDeltas = !opts.dryRun
370+
? snapshotDeltaMtimes(distDir)
371+
: new Map<string, number>();
372+
358373
// ------------------------------------------------------------------
359374
// Step 5 — generate_appcast dist/ (Sparkle EdDSA-signs each enclosure)
360375
// ------------------------------------------------------------------
@@ -449,18 +464,28 @@ export const releaseCommand = new Command("release")
449464
}
450465

451466
// ------------------------------------------------------------------
452-
// Step 6 — upload .dmg + every .delta in dist/ to R2
467+
// Step 6 — upload .dmg + new/changed .delta files in dist/ to R2
453468
//
454469
// generate_appcast writes signed <enclosure> entries for delta
455470
// patches alongside the full dmg; the appcast advertises both. If
456471
// we don't upload the .delta files, clients fetch the appcast,
457472
// pick a delta matching their current version, and hit a 404 — at
458473
// which point Sparkle reports "Update Error" instead of silently
459474
// falling back to the full dmg. So delta upload is not optional.
475+
//
476+
// Incremental upload: a .delta file ships only if it's both
477+
// (a) referenced by the freshly regenerated appcast, AND
478+
// (b) newly created by this run, OR its mtime changed (meaning
479+
// generate_appcast re-synthesized it because its source dmg
480+
// was rebuilt).
481+
// Deltas that already existed before this run with unchanged mtime
482+
// are assumed to be already on R2 from a previous release — they're
483+
// still listed in the appcast (so the feed stays valid for older
484+
// clients), but we don't re-PUT them. This makes a typical release
485+
// upload one .dmg + one .delta (vs current N-version, the most
486+
// common case for active users) instead of all historical deltas.
460487
// ------------------------------------------------------------------
461488
if (willUpload) {
462-
const distDir = path.join(ROOT_DIR, "dist");
463-
464489
// Whitelist delta uploads against the freshly-regenerated appcast.
465490
// generate_appcast does prune unreferenced delta files into
466491
// dist/old_updates/, but a resumed/partial release can leave stray
@@ -471,22 +496,42 @@ export const releaseCommand = new Command("release")
471496
const referencedDeltas = !opts.dryRun
472497
? collectReferencedDeltaBasenames(appcastDest)
473498
: new Set<string>();
474-
const allDeltas = existsSync(distDir)
475-
? readdirSync(distDir).filter((f) => f.endsWith(".delta"))
476-
: [];
499+
const postRunDeltas = existsSync(distDir)
500+
? snapshotDeltaMtimes(distDir)
501+
: new Map<string, number>();
477502
const deltaPaths: string[] = [];
478-
const skippedDeltas: string[] = [];
479-
for (const name of allDeltas) {
480-
if (opts.dryRun || referencedDeltas.has(name)) {
503+
const skippedUnreferenced: string[] = [];
504+
const skippedUnchanged: string[] = [];
505+
for (const [name, mtime] of postRunDeltas) {
506+
if (opts.dryRun) {
507+
deltaPaths.push(path.join(distDir, name));
508+
continue;
509+
}
510+
if (!referencedDeltas.has(name)) {
511+
skippedUnreferenced.push(name);
512+
continue;
513+
}
514+
const previous = preExistingDeltas.get(name);
515+
const isNewOrChanged = previous === undefined || previous !== mtime;
516+
if (isNewOrChanged) {
481517
deltaPaths.push(path.join(distDir, name));
482518
} else {
483-
skippedDeltas.push(name);
519+
skippedUnchanged.push(name);
484520
}
485521
}
486-
if (skippedDeltas.length > 0) {
522+
if (skippedUnreferenced.length > 0) {
487523
warn(
488-
`Skipping ${skippedDeltas.length} unreferenced delta file(s) in ` +
489-
`dist/ (not present in appcast): ${skippedDeltas.join(", ")}`
524+
`Skipping ${skippedUnreferenced.length} unreferenced delta file(s) ` +
525+
`in dist/ (not present in appcast): ${skippedUnreferenced.join(
526+
", "
527+
)}`
528+
);
529+
}
530+
if (skippedUnchanged.length > 0) {
531+
log(
532+
`Skipping ${skippedUnchanged.length} unchanged delta file(s) ` +
533+
`(already on R2 from a previous release): ` +
534+
skippedUnchanged.join(", ")
490535
);
491536
}
492537

@@ -502,8 +547,8 @@ export const releaseCommand = new Command("release")
502547

503548
const deltaSummary =
504549
deltaPaths.length > 0
505-
? ` + ${deltaPaths.length} delta(s)`
506-
: " (no referenced delta files in dist/)";
550+
? ` + ${deltaPaths.length} new/changed delta(s)`
551+
: " (no new delta files to upload)";
507552
await runStep(
508553
opts.dryRun,
509554
`Uploading ${dmgName}${deltaSummary} to R2`,
@@ -837,6 +882,27 @@ function collectReferencedDeltaBasenames(appcastPath: string): Set<string> {
837882
return referenced;
838883
}
839884

885+
// Snapshot the .delta files currently in dist/ along with their mtime in
886+
// milliseconds. Used by the release flow to diff before/after
887+
// generate_appcast so we only re-upload deltas that this run created or
888+
// refreshed — preexisting deltas with unchanged mtime are assumed to be
889+
// already in R2 from a previous release.
890+
function snapshotDeltaMtimes(distDir: string): Map<string, number> {
891+
const snapshot = new Map<string, number>();
892+
if (!existsSync(distDir)) return snapshot;
893+
for (const name of readdirSync(distDir)) {
894+
if (!name.endsWith(".delta")) continue;
895+
const full = path.join(distDir, name);
896+
try {
897+
const st = statSync(full);
898+
snapshot.set(name, st.mtimeMs);
899+
} catch {
900+
// File vanished between readdir and stat — ignore.
901+
}
902+
}
903+
return snapshot;
904+
}
905+
840906
// ---------------------------------------------------------------------------
841907
// Notarize + staple, with a useful recovery path on keychain failures
842908
// ---------------------------------------------------------------------------

website/public/appcast.xml

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,6 @@
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>
8157
<item>
8258
<title>1.0.20.0</title>
8359
<pubDate>Fri, 22 May 2026 04:27:46 +0800</pubDate>

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.22",
3+
"version": "1.0.20",
44
"chromiumVersion": "147.0.7727.135",
5-
"releasedAt": "2026-05-24",
5+
"releasedAt": "2026-05-22",
66
"platforms": {
77
"macArm64": {
88
"label": "macOS (Apple Silicon)",
9-
"url": "https://dao-release.msgbyte.com/dao-browser-1.0.22-mac-arm64.dmg"
9+
"url": "https://dao-release.msgbyte.com/dao-browser-1.0.20-mac-arm64.dmg"
1010
}
1111
},
1212
"default": "macArm64"

0 commit comments

Comments
 (0)