@stacksjs/desktop-build 0.75.135 → 0.75.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +113 -0
- package/dist/index.js +48 -6
- package/dist/probe/runner.js +60 -18
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,23 @@ export declare function plistValue(value: unknown, indent?: string): string;
|
|
|
48
48
|
* bundle must own. Returns the ignored keys so the caller can say so.
|
|
49
49
|
*/
|
|
50
50
|
export declare function renderUserlandPlistEntries(declared: Record<string, unknown>): { xml: string, ignored: string[] };
|
|
51
|
+
/**
|
|
52
|
+
* Render the `Info.plist` for the packaged `.app`.
|
|
53
|
+
*
|
|
54
|
+
* Every value an application chooses is escaped on the way in. It used to be
|
|
55
|
+
* interpolated raw, which made an app name as ordinary as `Repro & Co` emit
|
|
56
|
+
* `<string>Repro & Co</string>` - not well-formed XML, so macOS could not read
|
|
57
|
+
* the bundle's `Info.plist` at all and the app would not launch. `build:dmg`
|
|
58
|
+
* reported success either way, because nothing read back the file it had just
|
|
59
|
+
* written; the corruption first showed up as an app that silently refused to
|
|
60
|
+
* open. The entries an app declares in `app/Desktop/Info.plist.json` were
|
|
61
|
+
* already escaped by `renderUserlandPlistEntries`. The keys every build writes,
|
|
62
|
+
* which are the ones carrying a name and a version, were not.
|
|
63
|
+
*
|
|
64
|
+
* It lives here, rather than inline in the packaging script, so the output can
|
|
65
|
+
* be asserted directly instead of by building a DMG and mounting it.
|
|
66
|
+
*/
|
|
67
|
+
export declare function renderInfoPlist(options: InfoPlistOptions): { xml: string, ignored: string[] };
|
|
51
68
|
export declare function resolveDevWindowUrl(port: number, options?: OpenDevWindowOptions): string;
|
|
52
69
|
export declare function craftDevCommand(port: number, options?: OpenDevWindowOptions): string[];
|
|
53
70
|
export declare function createUpdateManifestUrl(baseUrl: string, channel?: string): string;
|
|
@@ -65,6 +82,29 @@ export declare function isMachO(path: string): boolean;
|
|
|
65
82
|
* Executables go in MacOS; everything else in Resources.
|
|
66
83
|
*/
|
|
67
84
|
export declare function bundleDirectoryFor(path: string): 'MacOS' | 'Resources';
|
|
85
|
+
/**
|
|
86
|
+
* The version the packaged app declares.
|
|
87
|
+
*
|
|
88
|
+
* This used to be `process.env.DESKTOP_APP_VERSION || '0.0.0'`, and nothing in
|
|
89
|
+
* the framework set that variable, so every app that did not export it itself
|
|
90
|
+
* shipped `CFBundleShortVersionString` of `0.0.0` - the version Finder shows,
|
|
91
|
+
* and the one a Sparkle-style updater compares to decide whether an update is
|
|
92
|
+
* an update. Every build looked like the same build. The DMG's filename and
|
|
93
|
+
* volume name carried it too, so `MyApp-0.0.0.dmg` overwrote the last release.
|
|
94
|
+
*
|
|
95
|
+
* The application states its version in `package.json`, which is where the
|
|
96
|
+
* framework reads every other thing about it, so reading it here is the whole
|
|
97
|
+
* fix. An explicit `DESKTOP_APP_VERSION` still wins: a pipeline stamping a
|
|
98
|
+
* build number or a release candidate is naming something `package.json` does
|
|
99
|
+
* not know.
|
|
100
|
+
*
|
|
101
|
+
* It is used verbatim rather than normalised. Apple documents
|
|
102
|
+
* `CFBundleShortVersionString` as three integers, and a semver prerelease such
|
|
103
|
+
* as `1.4.0-rc.2` is not that, but silently publishing an app as `1.4.0` when
|
|
104
|
+
* its project says `1.4.0-rc.2` would be the worse surprise: the two are
|
|
105
|
+
* different builds and an updater has to be able to tell them apart.
|
|
106
|
+
*/
|
|
107
|
+
export declare function desktopAppVersion(options?: DesktopAppVersionOptions): string;
|
|
68
108
|
/**
|
|
69
109
|
* The volume name for the app's DMG, which must never be the app's own name.
|
|
70
110
|
*
|
|
@@ -92,6 +132,31 @@ export declare function bundleDirectoryFor(path: string): 'MacOS' | 'Resources';
|
|
|
92
132
|
* somebody typed is how a build becomes unpredictable.
|
|
93
133
|
*/
|
|
94
134
|
export declare function dmgVolumeName(options: DmgVolumeOptions): string;
|
|
135
|
+
/**
|
|
136
|
+
* Everything `build:dmg` needs to name and sign a bundle, from flags first and
|
|
137
|
+
* the environment second.
|
|
138
|
+
*
|
|
139
|
+
* `build:dmg` took no flags at all: every one of these values could only be
|
|
140
|
+
* given as a `DESKTOP_*` environment variable, so packaging a signed release
|
|
141
|
+
* from a terminal meant exporting four of them, and anything scripted around
|
|
142
|
+
* that became a wrapper whose only job was to set variables and call `buddy`.
|
|
143
|
+
* Which is how an application ends up owning a build script (see
|
|
144
|
+
* stacksjs/stacks#2884, #2885, #2889, #2890 for what that script was working
|
|
145
|
+
* around).
|
|
146
|
+
*
|
|
147
|
+
* The sibling command for the Mac App Store, `desktop:apple`, already resolved
|
|
148
|
+
* its inputs this way - `options.appVersion || env('APPLE_APP_VERSION') ||
|
|
149
|
+
* metadata.version` - with a flag for each. This brings direct distribution to
|
|
150
|
+
* the same shape, so the two commands no longer disagree about how a Mac build
|
|
151
|
+
* is configured.
|
|
152
|
+
*
|
|
153
|
+
* The environment remains the right answer in CI, where these arrive as
|
|
154
|
+
* secrets; a flag is the right answer at a terminal. Neither is privileged over
|
|
155
|
+
* the application's own `package.json` by accident: a flag and a variable are
|
|
156
|
+
* both something a person stated for this build, so they win, and the manifest
|
|
157
|
+
* and `package.json` answer when nobody did.
|
|
158
|
+
*/
|
|
159
|
+
export declare function resolveDmgPackaging(inputs?: DmgPackagingInputs): DmgPackaging;
|
|
95
160
|
/**
|
|
96
161
|
* The `codesign` invocation for one target in a distributable bundle:
|
|
97
162
|
* hardened runtime (notarization rejects anything without it) and a secure
|
|
@@ -195,16 +260,64 @@ export declare interface DesktopLauncherCompileOptions {
|
|
|
195
260
|
outfile: string
|
|
196
261
|
minify?: boolean
|
|
197
262
|
}
|
|
263
|
+
export declare interface InfoPlistOptions {
|
|
264
|
+
appName: string
|
|
265
|
+
bundleId: string
|
|
266
|
+
version: string
|
|
267
|
+
executable: string
|
|
268
|
+
iconFile?: string
|
|
269
|
+
ownsLauncher: boolean
|
|
270
|
+
declared?: Record<string, unknown>
|
|
271
|
+
}
|
|
198
272
|
/** One Bun-compiled executable found in a bundle. */
|
|
199
273
|
export declare interface BundledRuntime {
|
|
200
274
|
name: string
|
|
201
275
|
bytes: number
|
|
202
276
|
}
|
|
277
|
+
export declare interface DesktopAppVersionOptions {
|
|
278
|
+
override?: string
|
|
279
|
+
declared?: unknown
|
|
280
|
+
}
|
|
203
281
|
export declare interface DmgVolumeOptions {
|
|
204
282
|
appName: string
|
|
205
283
|
version?: string
|
|
206
284
|
override?: string
|
|
207
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* The flags `build:dmg` accepts, as `parseOptions()` hands them over.
|
|
288
|
+
*
|
|
289
|
+
* Every value is `unknown` rather than `string` because these have not been
|
|
290
|
+
* validated yet: a flag declared but passed without an argument arrives as
|
|
291
|
+
* boolean `true`, which is the case `supplied` exists to reject.
|
|
292
|
+
*
|
|
293
|
+
* The index signature is what `parseOptions()` actually returns - every flag on
|
|
294
|
+
* the command line, including `--verbose` and the rest of `CliOptions`, not
|
|
295
|
+
* only the six read here. Narrowing it to just these six would describe a
|
|
296
|
+
* value this is never passed.
|
|
297
|
+
*/
|
|
298
|
+
export declare interface DmgPackagingFlags {
|
|
299
|
+
appName?: unknown
|
|
300
|
+
appVersion?: unknown
|
|
301
|
+
bundleId?: unknown
|
|
302
|
+
signingIdentity?: unknown
|
|
303
|
+
notaryProfile?: unknown
|
|
304
|
+
volumeName?: unknown
|
|
305
|
+
[flag: string]: unknown
|
|
306
|
+
}
|
|
307
|
+
export declare interface DmgPackagingInputs {
|
|
308
|
+
flags?: DmgPackagingFlags
|
|
309
|
+
env?: Record<string, string | undefined>
|
|
310
|
+
manifestTitle?: string
|
|
311
|
+
declaredVersion?: unknown
|
|
312
|
+
}
|
|
313
|
+
export declare interface DmgPackaging {
|
|
314
|
+
appName: string
|
|
315
|
+
bundleId: string
|
|
316
|
+
version: string
|
|
317
|
+
volumeName: string
|
|
318
|
+
signingIdentity?: string
|
|
319
|
+
notaryProfile?: string
|
|
320
|
+
}
|
|
208
321
|
export declare interface CodesignOptions {
|
|
209
322
|
identity: string
|
|
210
323
|
target: string
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var u=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(n){r=[n]}if(r)throw r[0];return t};function B(e,t,r={}){if(!t.trim())throw Error("Invite token is required");let n=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(n.searchParams.set("token",t),r.email)n.searchParams.set("email",r.email);if(r.team)n.searchParams.set("team",r.team);if(r.role)n.searchParams.set("role",r.role);if(r.expiresAt){let i=r.expiresAt instanceof Date?r.expiresAt:new Date(r.expiresAt);if(Number.isNaN(i.valueOf()))throw Error("Invite expiration must be a valid date");n.searchParams.set("expires",i.toISOString())}return n.toString()}async function ce(e,t){return Promise.all(e.map(async(r)=>{let n=typeof r==="string"?{email:r}:r;try{L(n.email);let i=await t.createToken(n),s=B(t.baseUrl,i,{email:n.email,team:n.team,role:n.role,expiresAt:t.expiresAt});return await t.send({recipient:n,url:s}),{recipient:n,url:s,delivered:!0}}catch(i){return{recipient:n,url:"",delivered:!1,error:i instanceof Error?i.message:String(i)}}}))}function L(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as j,createPublicKey as F,verify as O}from"crypto";import{mkdir as M,rename as P,rm as T,writeFile as z}from"fs/promises";import m from"process";import{dirname as N}from"path";async function ge(e){y(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let r=k(await t.json());return w(r,e.trustedKeys),b(r,e.target),K(r.version,e.currentVersion)>0?r:null}async function he(e,t,r=fetch,n,i){if(k(e),!n)throw Error("Desktop update staging requires trusted signing keys");w(e,n),b(e,i),y(e.url);let s=await r(e.url);if(!s.ok)throw Error(`Desktop update download failed with HTTP ${s.status}`);let o=new Uint8Array(await s.arrayBuffer());if(o.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${o.byteLength}`);if(j("sha256").update(o).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await M(N(t),{recursive:!0});let p=`${t}.download-${crypto.randomUUID()}`;try{await z(p,o,{mode:448}),await P(p,t)}catch(A){throw await T(p,{force:!0}),A}return{manifest:e,path:t,bytes:o.byteLength}}function K(e,t){let r=l(e),n=l(t);for(let i=0;i<3;i++){let s=r[i]-n[i];if(s!==0)return Math.sign(s)}return 0}function k(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(l(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function _(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function w(e,t){let r=t[e.signingKeyId];if(!r)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let n="ed25519-public:";if(!r.startsWith(n))throw Error("Desktop update signing key has an invalid encoding");try{let i=F({key:Buffer.from(r.slice(n.length),"base64url"),format:"der",type:"spki"}),s=Buffer.from(e.signature,"base64url");if(i.asymmetricKeyType!=="ed25519"||s.length!==64||!O(null,_(e),i,s))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function b(e,t={platform:m.platform,architecture:m.arch}){if(e.platform!==t.platform||e.architecture!==t.architecture)throw Error(`Desktop update is built for ${e.platform}-${e.architecture}, not this app's ${t.platform}-${t.architecture}`)}function l(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function y(e){let t=new URL(e),r=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw Error("Desktop updates require HTTPS outside loopback development")}var v=()=>{};function q(e=process.platform,t=process.arch){return V.find((r)=>r.platform===e&&r.architecture===t)}function ke(e,t=process.platform,r=process.arch){let n=q(t,r);if(!n)throw Error(`Desktop target ${t}/${r} is unsupported`);if(e==="stable"&&(n.status!=="stable"||n.signing!=="enforced"||n.platform==="darwin"&&n.notarization!=="enforced"||!n.installLaunchEvidence||!n.updateRollbackEvidence))throw Error(`Desktop target ${t}/${r} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return n}var V;var x=u(()=>{V=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 27.0 (local run, Craft v0.0.107 published archive)","macOS 15 runner (CI, Craft v0.0.48 from source)"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",updateRollbackEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The Stacks-built app bundle, DMG and PKG are unsigned while platform identities remain unprovisioned. The Craft runtime inside them is Developer ID-signed by Craft's own release workflow, which does not make the app signed.","Lifecycle evidence was re-run on 2026-10-03 against the published Craft v0.0.107 archive (craft-darwin-arm64.zip, sha256 98e1a7ae478a8900256add2884d05cc07aea2f3de7d550590d9458abd4fe7725) with `storage/framework/core/desktop/evidence/lifecycle.ts`: PKG and DMG install, a real Craft window launched from the installed bundle, update, a refused truncated update, rollback and uninstall all passed.","That run was local on macOS 27.0, not CI, and installed into the user domain (~/Applications) rather than /Applications. Gatekeeper was not exercised, since nothing carried a quarantine flag. The last CI lifecycle run for this target (https://github.com/stacksjs/stacks/actions/runs/29890804159, macOS 15 runner) used Craft v0.0.48 built from source."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-linux-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is not Authenticode-signed.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-windows-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]}]});import{closeSync as E,existsSync as d,openSync as S,readSync as C,statSync as G}from"fs";import{dirname as W,join as c}from"path";import a from"process";import{fileURLToPath as H}from"url";import{craftBinaryNotFoundMessage as Z,resolveCraftBinary as J}from"craft-native";function R(e=a.env.CRAFT_BIN){return J(e)}function Ee(e=a.env.CRAFT_BIN,t=(r)=>Bun.which(r)){let r=R(e);if(r!=="craft")return r;let n=t(r);if(!n)throw Error(Z(r));return n}function Se(e=a.cwd()){return d(c(e,I))}function Ce(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function Re(e=a.cwd()){let t=c(e,I);if(d(t))return t;let r=W(H(import.meta.url)),i=[c(r,"launcher.ts"),c(r,"launcher.js")].find((s)=>d(s));if(!i)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return i}function f(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function g(e,t=""){if(typeof e==="string")return`<string>${f(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
|
|
3
|
-
${e.map((n)=>`${t} ${
|
|
2
|
+
var g=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(n){r=[n]}if(r)throw r[0];return t};function U(e,t,r={}){if(!t.trim())throw Error("Invite token is required");let n=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(n.searchParams.set("token",t),r.email)n.searchParams.set("email",r.email);if(r.team)n.searchParams.set("team",r.team);if(r.role)n.searchParams.set("role",r.role);if(r.expiresAt){let i=r.expiresAt instanceof Date?r.expiresAt:new Date(r.expiresAt);if(Number.isNaN(i.valueOf()))throw Error("Invite expiration must be a valid date");n.searchParams.set("expires",i.toISOString())}return n.toString()}async function ke(e,t){return Promise.all(e.map(async(r)=>{let n=typeof r==="string"?{email:r}:r;try{M(n.email);let i=await t.createToken(n),s=U(t.baseUrl,i,{email:n.email,team:n.team,role:n.role,expiresAt:t.expiresAt});return await t.send({recipient:n,url:s}),{recipient:n,url:s,delivered:!0}}catch(i){return{recipient:n,url:"",delivered:!1,error:i instanceof Error?i.message:String(i)}}}))}function M(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as j,createPublicKey as V,verify as _}from"crypto";import{mkdir as z,rename as K,rm as q,writeFile as G}from"fs/promises";import v from"process";import{dirname as H}from"path";async function xe(e){S(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let r=D(await t.json());return x(r,e.trustedKeys),E(r,e.target),W(r.version,e.currentVersion)>0?r:null}async function Ee(e,t,r=fetch,n,i){if(D(e),!n)throw Error("Desktop update staging requires trusted signing keys");x(e,n),E(e,i),S(e.url);let s=await r(e.url);if(!s.ok)throw Error(`Desktop update download failed with HTTP ${s.status}`);let a=new Uint8Array(await s.arrayBuffer());if(a.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${a.byteLength}`);if(j("sha256").update(a).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await z(H(t),{recursive:!0});let p=`${t}.download-${crypto.randomUUID()}`;try{await G(p,a,{mode:448}),await K(p,t)}catch(d){throw await q(p,{force:!0}),d}return{manifest:e,path:t,bytes:a.byteLength}}function W(e,t){let r=f(e),n=f(t);for(let i=0;i<3;i++){let s=r[i]-n[i];if(s!==0)return Math.sign(s)}return 0}function D(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(f(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function Y(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function x(e,t){let r=t[e.signingKeyId];if(!r)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let n="ed25519-public:";if(!r.startsWith(n))throw Error("Desktop update signing key has an invalid encoding");try{let i=V({key:Buffer.from(r.slice(n.length),"base64url"),format:"der",type:"spki"}),s=Buffer.from(e.signature,"base64url");if(i.asymmetricKeyType!=="ed25519"||s.length!==64||!_(null,Y(e),i,s))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function E(e,t={platform:v.platform,architecture:v.arch}){if(e.platform!==t.platform||e.architecture!==t.architecture)throw Error(`Desktop update is built for ${e.platform}-${e.architecture}, not this app's ${t.platform}-${t.architecture}`)}function f(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function S(e){let t=new URL(e),r=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw Error("Desktop updates require HTTPS outside loopback development")}var I=()=>{};function J(e=process.platform,t=process.arch){return Z.find((r)=>r.platform===e&&r.architecture===t)}function Ie(e,t=process.platform,r=process.arch){let n=J(t,r);if(!n)throw Error(`Desktop target ${t}/${r} is unsupported`);if(e==="stable"&&(n.status!=="stable"||n.signing!=="enforced"||n.platform==="darwin"&&n.notarization!=="enforced"||!n.installLaunchEvidence||!n.updateRollbackEvidence))throw Error(`Desktop target ${t}/${r} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return n}var Z;var C=g(()=>{Z=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 27.0 (local run, Craft v0.0.107 published archive)","macOS 15 runner (CI, Craft v0.0.48 from source)"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",updateRollbackEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The Stacks-built app bundle, DMG and PKG are unsigned while platform identities remain unprovisioned. The Craft runtime inside them is Developer ID-signed by Craft's own release workflow, which does not make the app signed.","Lifecycle evidence was re-run on 2026-10-03 against the published Craft v0.0.107 archive (craft-darwin-arm64.zip, sha256 98e1a7ae478a8900256add2884d05cc07aea2f3de7d550590d9458abd4fe7725) with `storage/framework/core/desktop/evidence/lifecycle.ts`: PKG and DMG install, a real Craft window launched from the installed bundle, update, a refused truncated update, rollback and uninstall all passed.","That run was local on macOS 27.0, not CI, and installed into the user domain (~/Applications) rather than /Applications. Gatekeeper was not exercised, since nothing carried a quarantine flag. The last CI lifecycle run for this target (https://github.com/stacksjs/stacks/actions/runs/29890804159, macOS 15 runner) used Craft v0.0.48 built from source."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-linux-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is not Authenticode-signed.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-windows-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]}]});import{closeSync as P,existsSync as m,openSync as R,readSync as T,statSync as X}from"fs";import{dirname as Q,join as l}from"path";import u from"process";import{fileURLToPath as ee}from"url";import{craftBinaryNotFoundMessage as te,resolveCraftBinary as re}from"craft-native";function L(e=u.env.CRAFT_BIN){return re(e)}function Le(e=u.env.CRAFT_BIN,t=(r)=>Bun.which(r)){let r=L(e);if(r!=="craft")return r;let n=t(r);if(!n)throw Error(te(r));return n}function Ne(e=u.cwd()){return m(l(e,N))}function Fe(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function Oe(e=u.cwd()){let t=l(e,N);if(m(t))return t;let r=Q(ee(import.meta.url)),i=[l(r,"launcher.ts"),l(r,"launcher.js")].find((s)=>m(s));if(!i)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return i}function c(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function h(e,t=""){if(typeof e==="string")return`<string>${c(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
|
|
3
|
+
${e.map((n)=>`${t} ${h(n,`${t} `)}`).join(`
|
|
4
4
|
`)}
|
|
5
5
|
${t}</array>`}if(e&&typeof e==="object"){let r=Object.entries(e);if(r.length===0)return"<dict/>";return`<dict>
|
|
6
|
-
${r.map(([i,s])=>`${t} <key>${
|
|
6
|
+
${r.map(([i,s])=>`${t} <key>${c(i)}</key>${h(s,`${t} `)}`).join(`
|
|
7
7
|
`)}
|
|
8
|
-
${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function
|
|
9
|
-
${r.map(([i,s])=>` <key>${
|
|
10
|
-
`)}`,ignored:t}}function
|
|
8
|
+
${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function ne(e){let t=Object.keys(e).filter((i)=>A.has(i)),r=Object.entries(e).filter(([i])=>!A.has(i));if(r.length===0)return{xml:"",ignored:t};return{xml:`
|
|
9
|
+
${r.map(([i,s])=>` <key>${c(i)}</key>${h(s," ")}`).join(`
|
|
10
|
+
`)}`,ignored:t}}function Be(e){let t=e.declared??{},{xml:r,ignored:n}=ne(t),i=(O)=>Object.hasOwn(t,O),s=` <!-- The window talks to something this app started on loopback, so an
|
|
11
|
+
exception for 127.0.0.1 is all it needs. NSAllowsArbitraryLoads would
|
|
12
|
+
additionally permit every unencrypted host on the internet. -->
|
|
13
|
+
<key>NSAppTransportSecurity</key>
|
|
14
|
+
<dict>
|
|
15
|
+
<key>NSAllowsLocalNetworking</key><true/>
|
|
16
|
+
<key>NSExceptionDomains</key>
|
|
17
|
+
<dict>
|
|
18
|
+
<key>127.0.0.1</key>
|
|
19
|
+
<dict><key>NSExceptionAllowsInsecureHTTPLoads</key><true/></dict>
|
|
20
|
+
</dict>
|
|
21
|
+
</dict>`,a=` <!-- The window loads a remote URL, so the app has to be allowed to reach it. -->
|
|
22
|
+
<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>`,y=i("LSMinimumSystemVersion")?"":` <key>LSMinimumSystemVersion</key><string>11.0</string>
|
|
23
|
+
`,p=i("NSHighResolutionCapable")?"":` <key>NSHighResolutionCapable</key><true/>
|
|
24
|
+
`,d=i("NSAppTransportSecurity")?"":e.ownsLauncher?` <!-- The window talks to something this app started on loopback, so an
|
|
25
|
+
exception for 127.0.0.1 is all it needs. NSAllowsArbitraryLoads would
|
|
26
|
+
additionally permit every unencrypted host on the internet. -->
|
|
27
|
+
<key>NSAppTransportSecurity</key>
|
|
28
|
+
<dict>
|
|
29
|
+
<key>NSAllowsLocalNetworking</key><true/>
|
|
30
|
+
<key>NSExceptionDomains</key>
|
|
31
|
+
<dict>
|
|
32
|
+
<key>127.0.0.1</key>
|
|
33
|
+
<dict><key>NSExceptionAllowsInsecureHTTPLoads</key><true/></dict>
|
|
34
|
+
</dict>
|
|
35
|
+
</dict>`:` <!-- The window loads a remote URL, so the app has to be allowed to reach it. -->
|
|
36
|
+
<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>`,F=e.iconFile?`
|
|
37
|
+
<key>CFBundleIconFile</key><string>${c(e.iconFile)}</string>`:"",w=c(e.appName),b=c(e.version);return{xml:`<?xml version="1.0" encoding="UTF-8"?>
|
|
38
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
39
|
+
<plist version="1.0">
|
|
40
|
+
<dict>
|
|
41
|
+
<key>CFBundleName</key><string>${w}</string>
|
|
42
|
+
<key>CFBundleDisplayName</key><string>${w}</string>
|
|
43
|
+
<key>CFBundleIdentifier</key><string>${c(e.bundleId)}</string>
|
|
44
|
+
<key>CFBundleVersion</key><string>${b}</string>
|
|
45
|
+
<key>CFBundleShortVersionString</key><string>${b}</string>
|
|
46
|
+
<key>CFBundleExecutable</key><string>${c(e.executable)}</string>
|
|
47
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
48
|
+
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
49
|
+
${y}${p}${d}${F}${r}
|
|
50
|
+
</dict>
|
|
51
|
+
</plist>
|
|
52
|
+
`,ignored:n}}function ie(e,t={}){if(!Number.isInteger(e)||e<1||e>65535)throw RangeError(`Invalid desktop development port: ${e}`);let r=t.url||u.env.APP_URL;if(!r)return"https://stacks.test";let n=/^https?:\/\//.test(r)?r:`https://${r}`;return new URL(n).toString().replace(/\/$/,"")}function se(e,t={}){let r=[L(t.craftBin),ie(e,t),"--title",t.title||"Stacks","--width",String(t.width||1400),"--height",String(t.height||900)];if(t.hotReload!==!1)r.push("--hot-reload");if(t.devTools===!1)r.push("--no-devtools");if(t.darkMode)r.push("--dark");if(t.systemTray)r.push("--system-tray");if(t.hideDockIcon)r.push("--hide-dock-icon");if(t.menubarOnly)r.push("--menubar-only");return r}function Ue(e,t="stable"){if(!/^[a-z0-9-]+$/i.test(t))throw Error("Update channel may only contain letters, numbers, and hyphens");return new URL(`/desktop/updates/${t}.json`,/^https?:\/\//.test(e)?e:`https://${e}`).toString()}async function oe(e){Bun.spawn(e,{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).unref()}async function Me(e,t={},r=oe){return await r(se(e,t)),!0}function ue(e){try{let t=R(e,"r");try{let r=Buffer.alloc(4);if(T(t,r,0,4,0)<4)return!1;return pe.has(r.readUInt32BE(0))}finally{P(t)}}catch{return!1}}function je(e){return ue(e)?"MacOS":"Resources"}function le(e={}){let t=e.override?.trim();if(t)return t;return(typeof e.declared==="string"?e.declared.trim():"")||"0.0.0"}function de(e){let t=e.override?.trim();if(t)return t;let r=e.appName.trim(),n=e.version?.trim();return n?`${r} ${n}`:`${r} Installer`}function o(e){if(typeof e!=="string")return;return e.trim()||void 0}function Ve(e={}){let t=e.flags??{},r=e.env??{},n=o(t.appName)??o(r.DESKTOP_APP_NAME)??o(e.manifestTitle)??o(r.APP_NAME)??"Stacks",i=le({override:o(t.appVersion)??o(r.DESKTOP_APP_VERSION),declared:e.declaredVersion}),s=o(t.bundleId)??o(r.DESKTOP_BUNDLE_ID)??`sh.stacks.${n.toLowerCase().replace(/[^a-z0-9]+/g,"-")}`;return{appName:n,bundleId:s,version:i,volumeName:de({appName:n,version:i,override:o(t.volumeName)??o(r.DESKTOP_VOLUME_NAME)}),signingIdentity:o(t.signingIdentity)??o(r.DESKTOP_SIGNING_IDENTITY),notaryProfile:o(t.notaryProfile)??o(r.DESKTOP_NOTARY_PROFILE)}}function _e(e){return["codesign","--force",e.identity==="-"?"--timestamp=none":"--timestamp","--options","runtime",...e.entitlements?["--entitlements",e.entitlements]:[],"--sign",e.identity,e.target]}function ze(e,t=ge){let r=t(e);if(!r||r.size<k)return!1;return r.tail.includes(ae)}function ge(e){try{let{size:t}=X(e);if(t<k)return{size:t,tail:""};let r=R(e,"r");try{let n=Math.min(ce,t),i=Buffer.alloc(n);return T(r,i,0,n,t-n),{size:t,tail:i.toString("latin1")}}finally{P(r)}}catch{return null}}function Ke(e){if(e.length<2)return null;let t=[...e].sort((s,a)=>a.bytes-s.bytes),n=t.slice(1).length*k,i=t.map((s)=>`${s.name} (${(s.bytes/1024/1024).toFixed(1)} MB)`).join(", ");return`This bundle ships ${e.length} Bun-compiled executables: ${i}. Each embeds a full copy of the Bun runtime, so at least ${(n/1024/1024).toFixed(0)} MB of the bundle is the same runtime repeated. `+"Compiling one binary that dispatches on a subcommand \u2014 `MyApp agent`, `MyApp worker` \u2014 keeps the helpers as separate processes and pays for the runtime once."}var N="app/Desktop/launcher.ts",A,ae="---- Bun!",ce=8388608,k=41943040,pe;var fe=g(()=>{I();C();A=new Set(["CFBundleName","CFBundleDisplayName","CFBundleIdentifier","CFBundleVersion","CFBundleShortVersionString","CFBundleExecutable","CFBundlePackageType","CFBundleInfoDictionaryVersion","CFBundleIconFile"]);pe=new Set([4277009102,3472551422,4277009103,3489328638,3405691582,3199925962])});fe();export{ae as BUN_EXECUTABLE_MARKER,A as RESERVED_PLIST_KEYS,N as USERLAND_LAUNCHER,Ie as assertDesktopReleaseChannel,je as bundleDirectoryFor,xe as checkForDesktopUpdate,_e as codesignArgs,W as compareVersions,se as craftDevCommand,U as createInviteLink,Ue as createUpdateManifestUrl,Ke as describeRuntimeDuplication,le as desktopAppVersion,Fe as desktopLauncherCompileArgs,J as desktopSupport,Z as desktopSupportMatrix,de as dmgVolumeName,Ne as hasUserlandDesktopLauncher,ue as isMachO,ze as looksLikeBunExecutable,Me as openDevWindow,h as plistValue,Be as renderInfoPlist,ne as renderUserlandPlistEntries,L as resolveCraftBinary,Le as resolveCraftExecutable,Oe as resolveDesktopLauncher,ie as resolveDevWindowUrl,Ve as resolveDmgPackaging,ke as sendDesktopInvites,Ee as stageDesktopUpdate,Y as updateManifestPayload,D as validateUpdateManifest,x as verifyUpdateManifestSignature,c as xmlEscape};
|
package/dist/probe/runner.js
CHANGED
|
@@ -1,13 +1,55 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var O=(e,t,n)=>()=>{if(e)try{t=e(e=0)}catch(r){n=[r]}if(n)throw n[0];return t};function Ze(e,t,n={}){if(!t.trim())throw Error("Invite token is required");let r=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(r.searchParams.set("token",t),n.email)r.searchParams.set("email",n.email);if(n.team)r.searchParams.set("team",n.team);if(n.role)r.searchParams.set("role",n.role);if(n.expiresAt){let s=n.expiresAt instanceof Date?n.expiresAt:new Date(n.expiresAt);if(Number.isNaN(s.valueOf()))throw Error("Invite expiration must be a valid date");r.searchParams.set("expires",s.toISOString())}return r.toString()}async function Qt(e,t){return Promise.all(e.map(async(n)=>{let r=typeof n==="string"?{email:n}:n;try{Ye(r.email);let s=await t.createToken(r),o=Ze(t.baseUrl,s,{email:r.email,team:r.team,role:r.role,expiresAt:t.expiresAt});return await t.send({recipient:r,url:o}),{recipient:r,url:o,delivered:!0}}catch(s){return{recipient:r,url:"",delivered:!1,error:s instanceof Error?s.message:String(s)}}}))}function Ye(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as Xe,createPublicKey as Qe,verify as et}from"crypto";import{mkdir as tt,rename as nt,rm as rt,writeFile as st}from"fs/promises";import ae from"process";import{dirname as ot}from"path";async function on(e){de(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let n=ue(await t.json());return ce(n,e.trustedKeys),le(n,e.target),it(n.version,e.currentVersion)>0?n:null}async function an(e,t,n=fetch,r,s){if(ue(e),!r)throw Error("Desktop update staging requires trusted signing keys");ce(e,r),le(e,s),de(e.url);let o=await n(e.url);if(!o.ok)throw Error(`Desktop update download failed with HTTP ${o.status}`);let a=new Uint8Array(await o.arrayBuffer());if(a.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${a.byteLength}`);if(Xe("sha256").update(a).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await tt(ot(t),{recursive:!0});let u=`${t}.download-${crypto.randomUUID()}`;try{await st(u,a,{mode:448}),await nt(u,t)}catch(c){throw await rt(u,{force:!0}),c}return{manifest:e,path:t,bytes:a.byteLength}}function it(e,t){let n=_(e),r=_(t);for(let s=0;s<3;s++){let o=n[s]-r[s];if(o!==0)return Math.sign(o)}return 0}function ue(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(_(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function at(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function ce(e,t){let n=t[e.signingKeyId];if(!n)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let r="ed25519-public:";if(!n.startsWith(r))throw Error("Desktop update signing key has an invalid encoding");try{let s=Qe({key:Buffer.from(n.slice(r.length),"base64url"),format:"der",type:"spki"}),o=Buffer.from(e.signature,"base64url");if(s.asymmetricKeyType!=="ed25519"||o.length!==64||!et(null,at(e),s,o))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function le(e,t={platform:ae.platform,architecture:ae.arch}){if(e.platform!==t.platform||e.architecture!==t.architecture)throw Error(`Desktop update is built for ${e.platform}-${e.architecture}, not this app's ${t.platform}-${t.architecture}`)}function _(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function de(e){let t=new URL(e),n=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw Error("Desktop updates require HTTPS outside loopback development")}var pe=()=>{};function ct(e=process.platform,t=process.arch){return ut.find((n)=>n.platform===e&&n.architecture===t)}function cn(e,t=process.platform,n=process.arch){let r=ct(t,n);if(!r)throw Error(`Desktop target ${t}/${n} is unsupported`);if(e==="stable"&&(r.status!=="stable"||r.signing!=="enforced"||r.platform==="darwin"&&r.notarization!=="enforced"||!r.installLaunchEvidence||!r.updateRollbackEvidence))throw Error(`Desktop target ${t}/${n} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return r}var ut;var me=O(()=>{ut=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 27.0 (local run, Craft v0.0.107 published archive)","macOS 15 runner (CI, Craft v0.0.48 from source)"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",updateRollbackEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The Stacks-built app bundle, DMG and PKG are unsigned while platform identities remain unprovisioned. The Craft runtime inside them is Developer ID-signed by Craft's own release workflow, which does not make the app signed.","Lifecycle evidence was re-run on 2026-10-03 against the published Craft v0.0.107 archive (craft-darwin-arm64.zip, sha256 98e1a7ae478a8900256add2884d05cc07aea2f3de7d550590d9458abd4fe7725) with `storage/framework/core/desktop/evidence/lifecycle.ts`: PKG and DMG install, a real Craft window launched from the installed bundle, update, a refused truncated update, rollback and uninstall all passed.","That run was local on macOS 27.0, not CI, and installed into the user domain (~/Applications) rather than /Applications. Gatekeeper was not exercised, since nothing carried a quarantine flag. The last CI lifecycle run for this target (https://github.com/stacksjs/stacks/actions/runs/29890804159, macOS 15 runner) used Craft v0.0.48 built from source."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-linux-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is not Authenticode-signed.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-windows-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]}]});import{closeSync as ge,existsSync as z,openSync as he,readSync as be,statSync as lt}from"fs";import{dirname as dt,join as F}from"path";import E from"process";import{fileURLToPath as pt}from"url";import{craftBinaryNotFoundMessage as mt,resolveCraftBinary as ft}from"craft-native";function we(e=E.env.CRAFT_BIN){return ft(e)}function ye(e=E.env.CRAFT_BIN,t=(n)=>Bun.which(n)){let n=we(e);if(n!=="craft")return n;let r=t(n);if(!r)throw Error(mt(n));return r}function hn(e=E.cwd()){return z(F(e,ke))}function bn(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function wn(e=E.cwd()){let t=F(e,ke);if(z(t))return t;let n=dt(pt(import.meta.url)),s=[F(n,"launcher.ts"),F(n,"launcher.js")].find((o)=>z(o));if(!s)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return s}function H(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function q(e,t=""){if(typeof e==="string")return`<string>${H(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
|
|
3
|
-
${e.map((r)=>`${t} ${
|
|
2
|
+
var B=(e,t,n)=>()=>{if(e)try{t=e(e=0)}catch(r){n=[r]}if(n)throw n[0];return t};function Ze(e,t,n={}){if(!t.trim())throw Error("Invite token is required");let r=new URL("/invite",/^https?:\/\//.test(e)?e:`https://${e}`);if(r.searchParams.set("token",t),n.email)r.searchParams.set("email",n.email);if(n.team)r.searchParams.set("team",n.team);if(n.role)r.searchParams.set("role",n.role);if(n.expiresAt){let o=n.expiresAt instanceof Date?n.expiresAt:new Date(n.expiresAt);if(Number.isNaN(o.valueOf()))throw Error("Invite expiration must be a valid date");r.searchParams.set("expires",o.toISOString())}return r.toString()}async function rn(e,t){return Promise.all(e.map(async(n)=>{let r=typeof n==="string"?{email:n}:n;try{Xe(r.email);let o=await t.createToken(r),s=Ze(t.baseUrl,o,{email:r.email,team:r.team,role:r.role,expiresAt:t.expiresAt});return await t.send({recipient:r,url:s}),{recipient:r,url:s,delivered:!0}}catch(o){return{recipient:r,url:"",delivered:!1,error:o instanceof Error?o.message:String(o)}}}))}function Xe(e){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))throw Error(`Invalid invite email: ${e}`)}import{createHash as Qe,createPublicKey as et,verify as tt}from"crypto";import{mkdir as nt,rename as rt,rm as ot,writeFile as st}from"fs/promises";import ce from"process";import{dirname as it}from"path";async function ln(e){me(e.manifestUrl);let t=await(e.fetcher||fetch)(e.manifestUrl,{headers:{Accept:"application/json"}});if(!t.ok)throw Error(`Desktop update check failed with HTTP ${t.status}`);let n=le(await t.json());return de(n,e.trustedKeys),pe(n,e.target),at(n.version,e.currentVersion)>0?n:null}async function dn(e,t,n=fetch,r,o){if(le(e),!r)throw Error("Desktop update staging requires trusted signing keys");de(e,r),pe(e,o),me(e.url);let s=await n(e.url);if(!s.ok)throw Error(`Desktop update download failed with HTTP ${s.status}`);let a=new Uint8Array(await s.arrayBuffer());if(a.byteLength!==e.size)throw Error(`Desktop update size mismatch: expected ${e.size}, received ${a.byteLength}`);if(Qe("sha256").update(a).digest("hex")!==e.sha256.toLowerCase())throw Error("Desktop update checksum mismatch");await nt(it(t),{recursive:!0});let u=`${t}.download-${crypto.randomUUID()}`;try{await st(u,a,{mode:448}),await rt(u,t)}catch(c){throw await ot(u,{force:!0}),c}return{manifest:e,path:t,bytes:a.byteLength}}function at(e,t){let n=V(e),r=V(t);for(let o=0;o<3;o++){let s=n[o]-r[o];if(s!==0)return Math.sign(s)}return 0}function le(e){if(!e||typeof e!=="object")throw Error("Desktop update manifest must be an object");let t=e;if(V(t.version||""),typeof t.url!=="string")throw Error("Desktop update manifest requires a URL");if(typeof t.sha256!=="string"||!/^[a-f0-9]{64}$/i.test(t.sha256))throw Error("Desktop update manifest requires a SHA-256 checksum");if(!Number.isInteger(t.size)||t.size<0)throw Error("Desktop update manifest requires a non-negative integer size");if(typeof t.sourceRevision!=="string"||!/^[0-9a-f]{40}$/.test(t.sourceRevision))throw Error("Desktop update manifest requires a full source revision");if(typeof t.platform!=="string"||!["darwin","linux","win32"].includes(t.platform))throw Error("Desktop update manifest requires a supported platform identifier");if(typeof t.architecture!=="string"||!/^[a-z0-9_-]+$/.test(t.architecture))throw Error("Desktop update manifest requires an architecture");if(typeof t.signingKeyId!=="string"||!/^[a-zA-Z0-9._-]+$/.test(t.signingKeyId))throw Error("Desktop update manifest requires a signing key ID");if(typeof t.signature!=="string"||!/^[A-Za-z0-9_-]+$/.test(t.signature))throw Error("Desktop update manifest requires an Ed25519 signature");return t}function ut(e){return Buffer.from(JSON.stringify({version:e.version,url:e.url,sha256:e.sha256.toLowerCase(),size:e.size,sourceRevision:e.sourceRevision,platform:e.platform,architecture:e.architecture,signingKeyId:e.signingKeyId,publishedAt:e.publishedAt||null,notes:e.notes||null}),"utf8")}function de(e,t){let n=t[e.signingKeyId];if(!n)throw Error(`Desktop update signing key is not trusted: ${e.signingKeyId}`);let r="ed25519-public:";if(!n.startsWith(r))throw Error("Desktop update signing key has an invalid encoding");try{let o=et({key:Buffer.from(n.slice(r.length),"base64url"),format:"der",type:"spki"}),s=Buffer.from(e.signature,"base64url");if(o.asymmetricKeyType!=="ed25519"||s.length!==64||!tt(null,ut(e),o,s))throw Error("invalid")}catch{throw Error("Desktop update manifest signature verification failed")}}function pe(e,t={platform:ce.platform,architecture:ce.arch}){if(e.platform!==t.platform||e.architecture!==t.architecture)throw Error(`Desktop update is built for ${e.platform}-${e.architecture}, not this app's ${t.platform}-${t.architecture}`)}function V(e){let t=e.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);if(!t)throw Error(`Invalid semantic version: ${e}`);return[Number(t[1]),Number(t[2]),Number(t[3])]}function me(e){let t=new URL(e),n=["localhost","127.0.0.1","::1"].includes(t.hostname);if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw Error("Desktop updates require HTTPS outside loopback development")}var fe=()=>{};function lt(e=process.platform,t=process.arch){return ct.find((n)=>n.platform===e&&n.architecture===t)}function mn(e,t=process.platform,n=process.arch){let r=lt(t,n);if(!r)throw Error(`Desktop target ${t}/${n} is unsupported`);if(e==="stable"&&(r.status!=="stable"||r.signing!=="enforced"||r.platform==="darwin"&&r.notarization!=="enforced"||!r.installLaunchEvidence||!r.updateRollbackEvidence))throw Error(`Desktop target ${t}/${n} cannot be released as stable; complete stacksjs/stacks#2059, #2062, and #2063 first`);return r}var ct;var ge=B(()=>{ct=[{platform:"darwin",architecture:"arm64",status:"experimental",osVersions:["macOS 27.0 (local run, Craft v0.0.107 published archive)","macOS 15 runner (CI, Craft v0.0.48 from source)"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",updateRollbackEvidence:"https://github.com/stacksjs/stacks/blob/main/storage/framework/core/desktop/evidence/lifecycle-darwin-arm64.json",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The Stacks-built app bundle, DMG and PKG are unsigned while platform identities remain unprovisioned. The Craft runtime inside them is Developer ID-signed by Craft's own release workflow, which does not make the app signed.","Lifecycle evidence was re-run on 2026-10-03 against the published Craft v0.0.107 archive (craft-darwin-arm64.zip, sha256 98e1a7ae478a8900256add2884d05cc07aea2f3de7d550590d9458abd4fe7725) with `storage/framework/core/desktop/evidence/lifecycle.ts`: PKG and DMG install, a real Craft window launched from the installed bundle, update, a refused truncated update, rollback and uninstall all passed.","That run was local on macOS 27.0, not CI, and installed into the user domain (~/Applications) rather than /Applications. Gatekeeper was not exercised, since nothing carried a quarantine flag. The last CI lifecycle run for this target (https://github.com/stacksjs/stacks/actions/runs/29890804159, macOS 15 runner) used Craft v0.0.48 built from source."]},{platform:"darwin",architecture:"x64",status:"experimental",osVersions:["macOS 15 Intel runner"],packageFormat:"DMG + PKG",signing:"pending",notarization:"pending",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["Lifecycle fixtures are unsigned while platform identities remain unprovisioned.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-darwin-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"linux",architecture:"x64",status:"experimental",osVersions:["Ubuntu 24.04 runner"],packageFormat:"DEB",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is unsigned and no package repository support policy is published.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-linux-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]},{platform:"win32",architecture:"x64",status:"experimental",osVersions:["Windows Server 2025 runner"],packageFormat:"MSI + ZIP",signing:"pending",notarization:"not-applicable",installLaunchEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",updateRollbackEvidence:"https://github.com/stacksjs/stacks/actions/runs/29890804159",packagingEvidence:"https://github.com/stacksjs/protocol/blob/main/evidence/craft.json",blockingIssues:["https://github.com/stacksjs/stacks/issues/2062"],limitations:["The lifecycle fixture is not Authenticode-signed.","Lifecycle evidence was produced against Craft v0.0.48 built from source (bf75807). Craft now publishes a prebuilt craft-windows-x64.zip with every release, and no Stacks lifecycle run has re-verified a prebuilt release yet."]}]});import{closeSync as be,existsSync as q,openSync as we,readSync as ye,statSync as dt}from"fs";import{dirname as pt,join as L}from"path";import I from"process";import{fileURLToPath as mt}from"url";import{craftBinaryNotFoundMessage as ft,resolveCraftBinary as gt}from"craft-native";function ke(e=I.env.CRAFT_BIN){return gt(e)}function ve(e=I.env.CRAFT_BIN,t=(n)=>Bun.which(n)){let n=ke(e);if(n!=="craft")return n;let r=t(n);if(!r)throw Error(ft(n));return r}function kn(e=I.cwd()){return q(L(e,xe))}function vn(e){return["build","--compile",...e.minify===!1?[]:["--minify"],"--no-compile-autoload-bunfig","--no-compile-autoload-dotenv",e.entry,"--outfile",e.outfile]}function xn(e=I.cwd()){let t=L(e,xe);if(q(t))return t;let n=pt(mt(import.meta.url)),o=[L(n,"launcher.ts"),L(n,"launcher.js")].find((s)=>q(s));if(!o)throw Error("Desktop launcher entrypoint not found. Reinstall @stacksjs/desktop - the package must ship dist/launcher.js.");return o}function y(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function G(e,t=""){if(typeof e==="string")return`<string>${y(e)}</string>`;if(typeof e==="boolean")return e?"<true/>":"<false/>";if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`Unsupported Info.plist number: ${e}`);return Number.isInteger(e)?`<integer>${e}</integer>`:`<real>${e}</real>`}if(Array.isArray(e)){if(e.length===0)return"<array/>";return`<array>
|
|
3
|
+
${e.map((r)=>`${t} ${G(r,`${t} `)}`).join(`
|
|
4
4
|
`)}
|
|
5
5
|
${t}</array>`}if(e&&typeof e==="object"){let n=Object.entries(e);if(n.length===0)return"<dict/>";return`<dict>
|
|
6
|
-
${n.map(([s
|
|
6
|
+
${n.map(([o,s])=>`${t} <key>${y(o)}</key>${G(s,`${t} `)}`).join(`
|
|
7
7
|
`)}
|
|
8
|
-
${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function
|
|
9
|
-
${n.map(([s
|
|
10
|
-
`)}`,ignored:t}}function
|
|
8
|
+
${t}</dict>`}throw Error(`Unsupported Info.plist value: ${JSON.stringify(e)??String(e)}`)}function ht(e){let t=Object.keys(e).filter((o)=>he.has(o)),n=Object.entries(e).filter(([o])=>!he.has(o));if(n.length===0)return{xml:"",ignored:t};return{xml:`
|
|
9
|
+
${n.map(([o,s])=>` <key>${y(o)}</key>${G(s," ")}`).join(`
|
|
10
|
+
`)}`,ignored:t}}function Pn(e){let t=e.declared??{},{xml:n,ignored:r}=ht(t),o=(T)=>Object.hasOwn(t,T),s=` <!-- The window talks to something this app started on loopback, so an
|
|
11
|
+
exception for 127.0.0.1 is all it needs. NSAllowsArbitraryLoads would
|
|
12
|
+
additionally permit every unencrypted host on the internet. -->
|
|
13
|
+
<key>NSAppTransportSecurity</key>
|
|
14
|
+
<dict>
|
|
15
|
+
<key>NSAllowsLocalNetworking</key><true/>
|
|
16
|
+
<key>NSExceptionDomains</key>
|
|
17
|
+
<dict>
|
|
18
|
+
<key>127.0.0.1</key>
|
|
19
|
+
<dict><key>NSExceptionAllowsInsecureHTTPLoads</key><true/></dict>
|
|
20
|
+
</dict>
|
|
21
|
+
</dict>`,a=` <!-- The window loads a remote URL, so the app has to be allowed to reach it. -->
|
|
22
|
+
<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>`,i=o("LSMinimumSystemVersion")?"":` <key>LSMinimumSystemVersion</key><string>11.0</string>
|
|
23
|
+
`,u=o("NSHighResolutionCapable")?"":` <key>NSHighResolutionCapable</key><true/>
|
|
24
|
+
`,c=o("NSAppTransportSecurity")?"":e.ownsLauncher?` <!-- The window talks to something this app started on loopback, so an
|
|
25
|
+
exception for 127.0.0.1 is all it needs. NSAllowsArbitraryLoads would
|
|
26
|
+
additionally permit every unencrypted host on the internet. -->
|
|
27
|
+
<key>NSAppTransportSecurity</key>
|
|
28
|
+
<dict>
|
|
29
|
+
<key>NSAllowsLocalNetworking</key><true/>
|
|
30
|
+
<key>NSExceptionDomains</key>
|
|
31
|
+
<dict>
|
|
32
|
+
<key>127.0.0.1</key>
|
|
33
|
+
<dict><key>NSExceptionAllowsInsecureHTTPLoads</key><true/></dict>
|
|
34
|
+
</dict>
|
|
35
|
+
</dict>`:` <!-- The window loads a remote URL, so the app has to be allowed to reach it. -->
|
|
36
|
+
<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>`,d=e.iconFile?`
|
|
37
|
+
<key>CFBundleIconFile</key><string>${y(e.iconFile)}</string>`:"",l=y(e.appName),g=y(e.version);return{xml:`<?xml version="1.0" encoding="UTF-8"?>
|
|
38
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
39
|
+
<plist version="1.0">
|
|
40
|
+
<dict>
|
|
41
|
+
<key>CFBundleName</key><string>${l}</string>
|
|
42
|
+
<key>CFBundleDisplayName</key><string>${l}</string>
|
|
43
|
+
<key>CFBundleIdentifier</key><string>${y(e.bundleId)}</string>
|
|
44
|
+
<key>CFBundleVersion</key><string>${g}</string>
|
|
45
|
+
<key>CFBundleShortVersionString</key><string>${g}</string>
|
|
46
|
+
<key>CFBundleExecutable</key><string>${y(e.executable)}</string>
|
|
47
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
48
|
+
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
49
|
+
${i}${u}${c}${d}${n}
|
|
50
|
+
</dict>
|
|
51
|
+
</plist>
|
|
52
|
+
`,ignored:r}}function bt(e,t={}){if(!Number.isInteger(e)||e<1||e>65535)throw RangeError(`Invalid desktop development port: ${e}`);let n=t.url||I.env.APP_URL;if(!n)return"https://stacks.test";let r=/^https?:\/\//.test(n)?n:`https://${n}`;return new URL(r).toString().replace(/\/$/,"")}function wt(e,t={}){let n=[ke(t.craftBin),bt(e,t),"--title",t.title||"Stacks","--width",String(t.width||1400),"--height",String(t.height||900)];if(t.hotReload!==!1)n.push("--hot-reload");if(t.devTools===!1)n.push("--no-devtools");if(t.darkMode)n.push("--dark");if(t.systemTray)n.push("--system-tray");if(t.hideDockIcon)n.push("--hide-dock-icon");if(t.menubarOnly)n.push("--menubar-only");return n}function $n(e,t="stable"){if(!/^[a-z0-9-]+$/i.test(t))throw Error("Update channel may only contain letters, numbers, and hyphens");return new URL(`/desktop/updates/${t}.json`,/^https?:\/\//.test(e)?e:`https://${e}`).toString()}async function yt(e){Bun.spawn(e,{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).unref()}async function Rn(e,t={},n=yt){return await n(wt(e,t)),!0}function Pt(e){try{let t=we(e,"r");try{let n=Buffer.alloc(4);if(ye(t,n,0,4,0)<4)return!1;return xt.has(n.readUInt32BE(0))}finally{be(t)}}catch{return!1}}function Sn(e){return Pt(e)?"MacOS":"Resources"}function $t(e={}){let t=e.override?.trim();if(t)return t;return(typeof e.declared==="string"?e.declared.trim():"")||"0.0.0"}function Rt(e){let t=e.override?.trim();if(t)return t;let n=e.appName.trim(),r=e.version?.trim();return r?`${n} ${r}`:`${n} Installer`}function f(e){if(typeof e!=="string")return;return e.trim()||void 0}function En(e={}){let t=e.flags??{},n=e.env??{},r=f(t.appName)??f(n.DESKTOP_APP_NAME)??f(e.manifestTitle)??f(n.APP_NAME)??"Stacks",o=$t({override:f(t.appVersion)??f(n.DESKTOP_APP_VERSION),declared:e.declaredVersion}),s=f(t.bundleId)??f(n.DESKTOP_BUNDLE_ID)??`sh.stacks.${r.toLowerCase().replace(/[^a-z0-9]+/g,"-")}`;return{appName:r,bundleId:s,version:o,volumeName:Rt({appName:r,version:o,override:f(t.volumeName)??f(n.DESKTOP_VOLUME_NAME)}),signingIdentity:f(t.signingIdentity)??f(n.DESKTOP_SIGNING_IDENTITY),notaryProfile:f(t.notaryProfile)??f(n.DESKTOP_NOTARY_PROFILE)}}function Dn(e){return["codesign","--force",e.identity==="-"?"--timestamp=none":"--timestamp","--options","runtime",...e.entitlements?["--entitlements",e.entitlements]:[],"--sign",e.identity,e.target]}function In(e,t=St){let n=t(e);if(!n||n.size<W)return!1;return n.tail.includes(kt)}function St(e){try{let{size:t}=dt(e);if(t<W)return{size:t,tail:""};let n=we(e,"r");try{let r=Math.min(vt,t),o=Buffer.alloc(r);return ye(n,o,0,r,t-r),{size:t,tail:o.toString("latin1")}}finally{be(n)}}catch{return null}}function Mn(e){if(e.length<2)return null;let t=[...e].sort((s,a)=>a.bytes-s.bytes),r=t.slice(1).length*W,o=t.map((s)=>`${s.name} (${(s.bytes/1024/1024).toFixed(1)} MB)`).join(", ");return`This bundle ships ${e.length} Bun-compiled executables: ${o}. Each embeds a full copy of the Bun runtime, so at least ${(r/1024/1024).toFixed(0)} MB of the bundle is the same runtime repeated. `+"Compiling one binary that dispatches on a subcommand \u2014 `MyApp agent`, `MyApp worker` \u2014 keeps the helpers as separate processes and pays for the runtime once."}var xe="app/Desktop/launcher.ts",he,kt="---- Bun!",vt=8388608,W=41943040,xt;var Pe=B(()=>{fe();ge();he=new Set(["CFBundleName","CFBundleDisplayName","CFBundleIdentifier","CFBundleVersion","CFBundleShortVersionString","CFBundleExecutable","CFBundlePackageType","CFBundleInfoDictionaryVersion","CFBundleIconFile"]);xt=new Set([4277009102,3472551422,4277009103,3489328638,3405691582,3199925962])});import{existsSync as De,mkdirSync as Et,writeFileSync as $e}from"fs";import Re from"os";import{dirname as Dt,join as O,relative as Se,resolve as It}from"path";import p from"process";import{fileURLToPath as Mt}from"url";function Ie(e){return e.replaceAll("</script","<\\/script")}function K(e,t){return`<!doctype html>
|
|
11
53
|
<html lang="en">
|
|
12
54
|
<head>
|
|
13
55
|
<meta charset="utf-8">
|
|
@@ -48,19 +90,19 @@ ${n.map(([s,o])=>` <key>${H(s)}</key>${q(o," ")}`).join(`
|
|
|
48
90
|
<div id="countdown"></div>
|
|
49
91
|
</div>
|
|
50
92
|
<script type="application/json" id="probe-config">${JSON.stringify(t).replaceAll("<","\\u003c")}</script>
|
|
51
|
-
<script type="module">${
|
|
93
|
+
<script type="module">${Ie(e)}</script>
|
|
52
94
|
</body>
|
|
53
95
|
</html>
|
|
54
|
-
`}var
|
|
96
|
+
`}var J={idleMs:3000,loadMs:6000,cpuLoadMsPerFrame:6,instances:40000,interactionWindowMs:20000,tryNativeFullscreen:!0};var Z=1,Y=["available","unavailable","measured","requires-interaction","denied","error"],w=[["webgl2","WebGL2"],["webgpu","WebGPU"],["framePacing","Frame pacing"],["inputLatency","Input latency"],["audioLatency","Audio latency"],["gamepad","Gamepad API"],["fullscreen","Fullscreen"],["pointerLock","Pointer lock"]];function h(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function R(e){return typeof e==="string"&&e.trim().length>0}function Me(e){let t=[];if(!h(e))return["record is not an object"];if(e.schema!==1)t.push(`schema must be 1, got ${JSON.stringify(e.schema)}`);if(!R(e.measuredAt)||Number.isNaN(Date.parse(e.measuredAt)))t.push("measuredAt must be an ISO 8601 timestamp");let n=e.host;if(!h(n))t.push("host is missing");else{if(n.kind!=="craft"&&n.kind!=="browser")t.push(`host.kind must be craft or browser, got ${JSON.stringify(n.kind)}`);for(let i of["name","version","userAgent"])if(!R(n[i]))t.push(`host.${i} is required`);if(n.kind==="craft"&&R(n.version)&&!/^\d+\.\d+\.\d+/.test(n.version))t.push(`host.version must be a Craft release version, got ${JSON.stringify(n.version)}`)}let r=e.os;if(!h(r))t.push("os is missing");else for(let i of["platform","arch","name","version"])if(!R(r[i]))t.push(`os.${i} is required`);let o=e.machine;if(!h(o))t.push("machine is missing");else{for(let u of["model","cpu"])if(!R(o[u]))t.push(`machine.${u} is required`);let i=o.display;if(!h(i))t.push("machine.display is missing");else{if(i.refreshHz!==null&&!(typeof i.refreshHz==="number"&&i.refreshHz>0))t.push("machine.display.refreshHz must be a positive number or null");if(!(typeof i.devicePixelRatio==="number"&&i.devicePixelRatio>0))t.push("machine.display.devicePixelRatio must be a positive number")}}let s=e.run;if(!h(s))t.push("run is missing");else{for(let i of["idleMs","loadMs","cpuLoadMsPerFrame","triangles","interactionWindowMs"])if(!(typeof s[i]==="number"&&s[i]>=0))t.push(`run.${i} must be a non-negative number`);if(typeof s.sawRealInput!=="boolean")t.push("run.sawRealInput must be a boolean")}let a=e.probes;if(!h(a))return t.push("probes is missing"),t;for(let[i]of w){let u=a[i];if(!h(u)){t.push(`probes.${i} is missing`);continue}if(!Y.includes(u.status))t.push(`probes.${i}.status must be one of ${Y.join(", ")}, got ${JSON.stringify(u.status)}`);if(!R(u.note))t.push(`probes.${i}.note is required: say what was observed, even on success`);if(u.data!==void 0&&!h(u.data))t.push(`probes.${i}.data must be an object when present`);if(u.status==="measured"&&!h(u.data))t.push(`probes.${i} is measured but carries no data`)}for(let i of Object.keys(a))if(!w.some(([u])=>u===i))t.push(`probes.${i} is not a known probe`);return t}function A(e,t="probe record"){let n=Me(e);if(n.length>0)throw Error(`${t} is invalid:
|
|
55
97
|
- ${n.join(`
|
|
56
|
-
- `)}`);return e}var Ie=[24,30,48,50,60,72,75,90,100,120,144,165,240];function y(e,t){if(e.length===0)return null;if(!Number.isFinite(t)||t<0||t>100)throw RangeError(`percentile must be between 0 and 100, got ${t}`);let n=[...e].sort((u,c)=>u-c),r=t/100*(n.length-1),s=Math.floor(r),o=Math.ceil(r),a=n[s],i=n[o];return a+(i-a)*(r-s)}function T(e){let t=[];for(let n=1;n<e.length;n++)t.push(e[n]-e[n-1]);return t}function Ae(e,t=1,n=50){if(t<=0||!Number.isFinite(t))throw RangeError(`bucketMs must be positive, got ${t}`);if(!Number.isInteger(n)||n<1)throw RangeError(`buckets must be a positive integer, got ${n}`);let r=Array.from({length:n},()=>0),s=0;for(let o of e){let a=Math.floor(Math.max(0,o)/t);if(a>=n)s++;else r[a]++}return{bucketMs:t,counts:r,overflow:s}}function Z(e,t=0.03){if(!(e>0))throw RangeError(`period must be positive, got ${e}`);let n=1000/e,r=n,s=Number.POSITIVE_INFINITY;for(let o of Ie){let a=Math.abs(n-o)/o;if(a<s)r=o,s=a}return s<=t?r:Math.round(n*100)/100}function Fe(e,t){if(!(t>0))throw RangeError(`period must be positive, got ${t}`);let n=0;for(let r of e)n+=Math.max(0,Math.round(r/t)-1);return n}function I(e,t){if(e.length<3)return null;if(!(t>0))throw RangeError(`targetHz must be positive, got ${t}`);let n=T(e),r=e[e.length-1]-e[0],s=1000/t,o=(a)=>Math.round(a*100)/100;return{frames:e.length,durationMs:o(r),fps:o(r>0?n.length*1000/r:0),p50Ms:o(y(n,50)),p95Ms:o(y(n,95)),p99Ms:o(y(n,99)),maxMs:o(Math.max(...n)),targetPeriodMs:o(s),targetHz:t,droppedFrames:Fe(n,s),longFrames:n.filter((a)=>a>s*1.5).length,histogram:Ae(n)}}function A(e){let t=e.filter((r)=>Number.isFinite(r)&&r>=0);if(t.length===0)return null;let n=(r)=>Math.round(r*100)/100;return{samples:t.length,p50Ms:n(y(t,50)),p95Ms:n(y(t,95)),maxMs:n(Math.max(...t))}}function Y(e){let t=e?.match(/@\s*([\d.]+)\s*Hz/i);if(!t)return null;let n=Number(t[1]);return Number.isFinite(n)&&n>0?n:null}function ee(e){let t=[["Edge",/Edg\/([\d.]+)/],["Firefox",/Firefox\/([\d.]+)/],["Chrome",/Chrome\/([\d.]+)/],["Safari",/Version\/([\d.]+).*Safari\//],["WebKit",/AppleWebKit\/([\d.]+)/]];for(let[n,r]of t){let s=e.match(r);if(s)return{name:n,version:s[1]}}return{name:"unknown",version:"unknown"}}function X(e){return typeof e==="number"?`${Math.round(e*1e4)/10} ms`:"not reported"}function R(e){return e.detail?`${e.outcome} (${e.detail})`:e.outcome}function Ce(e){let t=e.webgl2;if(!t.ok)return{status:"unavailable",note:`No WebGL2 context: ${t.error??"unknown reason"}.`,data:{...t}};let n=t.unmaskedRenderer||t.renderer;return{status:"available",note:`WebGL2 context created (${t.version}); renderer "${n}", max texture ${t.maxTextureSize}, MSAA up to ${t.maxSamples}x.`,data:{...t}}}function Le(e){let t=e.webgpu;if(!t.present)return{status:"unavailable",note:"navigator.gpu is undefined.",data:{...t}};if(!t.adapter||!t.device)return{status:"unavailable",note:`navigator.gpu exists but ${t.adapter?"requestDevice()":"requestAdapter()"} failed: ${t.error??"no reason given"}.`,data:{...t}};return{status:"available",note:`Adapter (${[...new Set([t.vendor,t.architecture].filter(Boolean))].join(" ")||"unnamed adapter"}) and device created; maxTextureDimension2D ${t.maxTextureDimension2D??"unknown"}.`,data:{...t}}}function Oe(e,t){let{frames:n,config:r}=e;if(n.idle.length<3||n.load.length<3)return{status:"error",note:`Too few animation frames to measure (${n.idle.length} idle, ${n.load.length} under load).`};let s=Z(y(T(n.idle),50)),o=t.machine.display.refreshHz??s,a=I(n.idle,o),i=I(n.load,o),u=I(n.load,s),c={rafHz:s,displayHz:o,timerResolutionMs:e.timerResolutionMs,triangles:n.triangles,cpuLoadMsPerFrame:r.cpuLoadMsPerFrame,hiddenDuringSampling:n.hiddenDuringSampling,idle:a,load:i,loadAtRafRate:{droppedFrames:u.droppedFrames,longFrames:u.longFrames}};if(n.hiddenDuringSampling)return{status:"error",note:"The page was hidden while sampling, which throttles requestAnimationFrame; these numbers are not representative. Re-run with the window visible.",data:c};let d=s<o-1?`requestAnimationFrame ran at ${s} Hz on a ${o} Hz display, so a game cannot reach the panel's rate. `:`requestAnimationFrame ran at the display's ${o} Hz. `,p=n.triangles>0?`${n.triangles.toLocaleString("en-US")} triangles`:"a 2D canvas fallback";return{status:"measured",note:`${d}Under load (${p} + ${r.cpuLoadMsPerFrame} ms CPU per frame): p50 ${i.p50Ms} ms, p95 ${i.p95Ms} ms, p99 ${i.p99Ms} ms; ${u.droppedFrames} frames dropped at ${s} Hz and ${i.droppedFrames} at the display's ${o} Hz over ${Math.round(i.durationMs/100)/10} s.${e.timerResolutionMs>=0.5?` The page clock is clamped to ${e.timerResolutionMs} ms, so intervals read as whole milliseconds.`:""}`,data:c}}var Q=5;function Te(e){let{input:t}=e,n={eventTimeStampComparable:e.eventTimeStampComparable,eventTimingSupported:t.eventTimingSupported,timerResolutionMs:e.timerResolutionMs};if(!e.eventTimeStampComparable)return{status:"error",note:"event.timeStamp does not share performance.now()'s time origin, so event-to-frame time cannot be computed.",data:n};let r={pointerdown:t.pointerdown,pointermove:t.pointermove,keydown:t.keydown},s=Object.values(r).reduce((i,u)=>i+u.length,0);if(s===0)return{status:"requires-interaction",note:`${e.interaction.skipped?"The interactive window was skipped":`No trusted input reached the page in the ${e.interaction.windowMs/1000} s window`}, and synthetic events are not counted. Observable: event.timeStamp is on the performance.now() clock, timer resolution ${e.timerResolutionMs} ms, Event Timing API ${t.eventTimingSupported?"supported":"not supported"}.`,data:n};let o={...n},a=[];for(let[i,u]of Object.entries(r)){let c=A(u.map((p)=>p.toFrameMs)),d=A(u.map((p)=>p.toPostFrameMs));if(o[i]={toFrame:c,toPostFrame:d},c)a.push(`${i} p50 ${c.p50Ms} ms to next frame (${c.samples} ${c.samples===1?"sample":"samples"})`)}if(o.eventTiming=A(t.eventTimingDurations),s<Q)return{status:"requires-interaction",note:`Only ${s} trusted input ${s===1?"event":"events"} arrived, fewer than the ${Q} needed to summarise. Seen: ${a.join("; ")}. Re-run and use the panel's buttons and keys.`,data:o};return{status:"measured",note:`Event timestamp to the next rAF callback: ${a.join("; ")}. Real input from the OS; no synthetic events.`,data:o}}function Ne(e){let t=e.audio;if(!t.present)return{status:"unavailable",note:"window.AudioContext is undefined."};if(t.error||!t.initial)return{status:"error",note:`Creating an AudioContext failed: ${t.error??"unknown reason"}.`};let n=t.afterGesturedResume??t.afterUngesturedResume??t.initial,r={initial:t.initial,afterUngesturedResume:t.afterUngesturedResume??null,afterGesturedResume:t.afterGesturedResume??null},s=`baseLatency ${X(n.baseLatency)}, outputLatency ${X(n.outputLatency)} at ${n.sampleRate} Hz`,o=t.afterUngesturedResume?.state==="running"?"The context started without a user gesture":"The context stayed suspended without a user gesture";if(n.state!=="running")return{status:"requires-interaction",note:`${o}, so device latency was not observable. Reported before running: ${s}.`,data:r};if(n.baseLatency===null&&n.outputLatency===null)return{status:"unavailable",note:`${o}, but neither baseLatency nor outputLatency is implemented.`,data:r};return{status:"measured",note:`${o}. ${s}${n.outputLatency===null?" (outputLatency not implemented)":""}.`,data:r}}function Be(e){let t=e.gamepad;if(!t.present)return{status:"unavailable",note:"navigator.getGamepads is undefined."};return{status:"available",note:t.connected>0?`navigator.getGamepads() is present and reported ${t.connected} controller(s).`:"navigator.getGamepads() is present; no controller was connected, so axis and button input was not exercised.",data:{...t,secureContext:e.isSecureContext}}}function je(e){let t=e.fullscreen,n=t.nativeBridge.outcome==="not-attempted"||t.nativeBridge.outcome==="no-api"?"":` Craft's own window.craft.window.setFullscreen(true), which needs no gesture: ${R(t.nativeBridge)}.`,r={...t};if(!t.elementApi&&!t.webkitElementApi)return{status:"unavailable",note:`Element.requestFullscreen and webkitRequestFullscreen are both undefined, so the standard Fullscreen API engines call does nothing.${n}`,data:r};let s=`Without a gesture: ${R(t.ungestured)}.`;switch(t.gestured.outcome){case"entered":return{status:"available",note:`requestFullscreen() from a real click entered fullscreen. ${s}${n}`,data:r};case"rejected":case"timeout":return{status:"denied",note:`requestFullscreen() from a real click: ${R(t.gestured)}. ${s}${n}`,data:r};default:return{status:"requires-interaction",note:`The API exists (fullscreenEnabled ${String(t.enabled)}) but needs a real click to test. ${s}${n}`,data:r}}}function Ue(e){let t=e.pointerLock,n={...t};if(!t.api)return{status:"unavailable",note:"Element.requestPointerLock is undefined.",data:n};let r=`Without a gesture: ${R(t.ungestured)}.`;switch(t.gestured.outcome){case"entered":return{status:"available",note:`requestPointerLock() from a real click locked the pointer. ${r}`,data:n};case"rejected":case"timeout":return{status:"denied",note:`requestPointerLock() from a real click: ${R(t.gestured)}. ${r}`,data:n};default:return{status:"requires-interaction",note:`The API exists but needs a real click to test. ${r}`,data:n}}}function te(e,t){return{schema:J,measuredAt:t.measuredAt,host:{...t.host,userAgent:e.userAgent},os:t.os,machine:{model:t.machine.model,cpu:t.machine.cpu,display:{...t.machine.display,devicePixelRatio:e.devicePixelRatio}},run:{idleMs:e.config.idleMs,loadMs:e.config.loadMs,cpuLoadMsPerFrame:e.config.cpuLoadMsPerFrame,triangles:e.frames.triangles,interactionWindowMs:e.interaction.windowMs,sawRealInput:e.interaction.sawRealInput},probes:{webgl2:Ce(e),webgpu:Le(e),framePacing:Oe(e,t),inputLatency:Te(e),audioLatency:Ne(e),gamepad:Be(e),fullscreen:je(e),pointerLock:Ue(e)}}}import{readdirSync as _e,readFileSync as ze}from"fs";import{join as He}from"path";import{fileURLToPath as qe}from"url";var S=qe(new URL("./results",import.meta.url));function N(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ne(e){return`${e.host.kind==="craft"?"craft":`browser-${N(e.host.name)}`}-${N(e.os.platform)}-${N(e.os.arch)}.json`}function Kt(e=S){let t;try{t=_e(e).filter((n)=>n.endsWith(".json")).sort()}catch{return[]}return t.map((n)=>{let r=He(e,n);return D(JSON.parse(ze(r,"utf8")),r)})}var We="<!-- interactive-capability:begin -->",re="<!-- interactive-capability:end -->",B="buddy desktop:probe",Ge={darwin:"macOS",linux:"Linux",win32:"Windows"};function se(e){return e.host.kind==="craft"?`Craft ${e.host.version}`:`${e.host.name} ${e.host.version}`}function oe(e){return`${Ge[e.os.platform]??e.os.name} ${e.os.version} ${e.os.arch}, ${e.machine.cpu} (${e.machine.model})`}function Ve(e){return[...e].sort((t,n)=>{if(t.host.kind!==n.host.kind)return t.host.kind==="craft"?-1:1;return`${t.host.name} ${t.os.platform} ${t.os.arch}`.localeCompare(`${n.host.name} ${n.os.platform} ${n.os.arch}`)})}function P(e){return typeof e==="number"&&Number.isFinite(e)?e:null}function ie(e,...t){let n=e;for(let r of t){if(typeof n!=="object"||n===null)return;n=n[r]}return n}function U(e,t){let n=t.data;if(t.status!=="measured"||!n)return null;switch(e){case"framePacing":{let r=P(n.rafHz),s=P(n.displayHz),o=P(ie(n,"load","p99Ms"));if(r===null||s===null||o===null)return null;return`${r} Hz rAF on a ${s} Hz display, p99 ${o} ms under load`}case"inputLatency":{for(let r of["pointerdown","keydown","pointermove"]){let s=P(ie(n,r,"toFrame","p50Ms"));if(s!==null)return`${r} p50 ${s} ms to next frame`}return null}case"audioLatency":{let r=n.afterGesturedResume??n.afterUngesturedResume,s=P(r?.baseLatency),o=P(r?.outputLatency),a=(i)=>i===null?"n/a":`${Math.round(i*1e4)/10} ms`;return`base ${a(s)}, output ${a(o)}`}default:return null}}function Ke(e,t){if(!t)return"not probed";let n=U(e,t);return n?`**${t.status}** - ${n}`:`**${t.status}**`}function j(e){return e.replaceAll("|","\\|").replaceAll(`
|
|
57
|
-
`," ")}function
|
|
58
|
-
`);n.push(`| Probe | ${t.map((r)=>
|
|
59
|
-
`)}function
|
|
98
|
+
- `)}`);return e}var Ae=[24,30,48,50,60,72,75,90,100,120,144,165,240];function x(e,t){if(e.length===0)return null;if(!Number.isFinite(t)||t<0||t>100)throw RangeError(`percentile must be between 0 and 100, got ${t}`);let n=[...e].sort((u,c)=>u-c),r=t/100*(n.length-1),o=Math.floor(r),s=Math.ceil(r),a=n[o],i=n[s];return a+(i-a)*(r-o)}function _(e){let t=[];for(let n=1;n<e.length;n++)t.push(e[n]-e[n-1]);return t}function Fe(e,t=1,n=50){if(t<=0||!Number.isFinite(t))throw RangeError(`bucketMs must be positive, got ${t}`);if(!Number.isInteger(n)||n<1)throw RangeError(`buckets must be a positive integer, got ${n}`);let r=Array.from({length:n},()=>0),o=0;for(let s of e){let a=Math.floor(Math.max(0,s)/t);if(a>=n)o++;else r[a]++}return{bucketMs:t,counts:r,overflow:o}}function X(e,t=0.03){if(!(e>0))throw RangeError(`period must be positive, got ${e}`);let n=1000/e,r=n,o=Number.POSITIVE_INFINITY;for(let s of Ae){let a=Math.abs(n-s)/s;if(a<o)r=s,o=a}return o<=t?r:Math.round(n*100)/100}function Ce(e,t){if(!(t>0))throw RangeError(`period must be positive, got ${t}`);let n=0;for(let r of e)n+=Math.max(0,Math.round(r/t)-1);return n}function F(e,t){if(e.length<3)return null;if(!(t>0))throw RangeError(`targetHz must be positive, got ${t}`);let n=_(e),r=e[e.length-1]-e[0],o=1000/t,s=(a)=>Math.round(a*100)/100;return{frames:e.length,durationMs:s(r),fps:s(r>0?n.length*1000/r:0),p50Ms:s(x(n,50)),p95Ms:s(x(n,95)),p99Ms:s(x(n,99)),maxMs:s(Math.max(...n)),targetPeriodMs:s(o),targetHz:t,droppedFrames:Ce(n,o),longFrames:n.filter((a)=>a>o*1.5).length,histogram:Fe(n)}}function C(e){let t=e.filter((r)=>Number.isFinite(r)&&r>=0);if(t.length===0)return null;let n=(r)=>Math.round(r*100)/100;return{samples:t.length,p50Ms:n(x(t,50)),p95Ms:n(x(t,95)),maxMs:n(Math.max(...t))}}function Q(e){let t=e?.match(/@\s*([\d.]+)\s*Hz/i);if(!t)return null;let n=Number(t[1]);return Number.isFinite(n)&&n>0?n:null}function ne(e){let t=[["Edge",/Edg\/([\d.]+)/],["Firefox",/Firefox\/([\d.]+)/],["Chrome",/Chrome\/([\d.]+)/],["Safari",/Version\/([\d.]+).*Safari\//],["WebKit",/AppleWebKit\/([\d.]+)/]];for(let[n,r]of t){let o=e.match(r);if(o)return{name:n,version:o[1]}}return{name:"unknown",version:"unknown"}}function ee(e){return typeof e==="number"?`${Math.round(e*1e4)/10} ms`:"not reported"}function E(e){return e.detail?`${e.outcome} (${e.detail})`:e.outcome}function Le(e){let t=e.webgl2;if(!t.ok)return{status:"unavailable",note:`No WebGL2 context: ${t.error??"unknown reason"}.`,data:{...t}};let n=t.unmaskedRenderer||t.renderer;return{status:"available",note:`WebGL2 context created (${t.version}); renderer "${n}", max texture ${t.maxTextureSize}, MSAA up to ${t.maxSamples}x.`,data:{...t}}}function Ne(e){let t=e.webgpu;if(!t.present)return{status:"unavailable",note:"navigator.gpu is undefined.",data:{...t}};if(!t.adapter||!t.device)return{status:"unavailable",note:`navigator.gpu exists but ${t.adapter?"requestDevice()":"requestAdapter()"} failed: ${t.error??"no reason given"}.`,data:{...t}};return{status:"available",note:`Adapter (${[...new Set([t.vendor,t.architecture].filter(Boolean))].join(" ")||"unnamed adapter"}) and device created; maxTextureDimension2D ${t.maxTextureDimension2D??"unknown"}.`,data:{...t}}}function Oe(e,t){let{frames:n,config:r}=e;if(n.idle.length<3||n.load.length<3)return{status:"error",note:`Too few animation frames to measure (${n.idle.length} idle, ${n.load.length} under load).`};let o=X(x(_(n.idle),50)),s=t.machine.display.refreshHz??o,a=F(n.idle,s),i=F(n.load,s),u=F(n.load,o),c={rafHz:o,displayHz:s,timerResolutionMs:e.timerResolutionMs,triangles:n.triangles,cpuLoadMsPerFrame:r.cpuLoadMsPerFrame,hiddenDuringSampling:n.hiddenDuringSampling,idle:a,load:i,loadAtRafRate:{droppedFrames:u.droppedFrames,longFrames:u.longFrames}};if(n.hiddenDuringSampling)return{status:"error",note:"The page was hidden while sampling, which throttles requestAnimationFrame; these numbers are not representative. Re-run with the window visible.",data:c};let d=o<s-1?`requestAnimationFrame ran at ${o} Hz on a ${s} Hz display, so a game cannot reach the panel's rate. `:`requestAnimationFrame ran at the display's ${s} Hz. `,l=n.triangles>0?`${n.triangles.toLocaleString("en-US")} triangles`:"a 2D canvas fallback";return{status:"measured",note:`${d}Under load (${l} + ${r.cpuLoadMsPerFrame} ms CPU per frame): p50 ${i.p50Ms} ms, p95 ${i.p95Ms} ms, p99 ${i.p99Ms} ms; ${u.droppedFrames} frames dropped at ${o} Hz and ${i.droppedFrames} at the display's ${s} Hz over ${Math.round(i.durationMs/100)/10} s.${e.timerResolutionMs>=0.5?` The page clock is clamped to ${e.timerResolutionMs} ms, so intervals read as whole milliseconds.`:""}`,data:c}}var te=5;function Te(e){let{input:t}=e,n={eventTimeStampComparable:e.eventTimeStampComparable,eventTimingSupported:t.eventTimingSupported,timerResolutionMs:e.timerResolutionMs};if(!e.eventTimeStampComparable)return{status:"error",note:"event.timeStamp does not share performance.now()'s time origin, so event-to-frame time cannot be computed.",data:n};let r={pointerdown:t.pointerdown,pointermove:t.pointermove,keydown:t.keydown},o=Object.values(r).reduce((i,u)=>i+u.length,0);if(o===0)return{status:"requires-interaction",note:`${e.interaction.skipped?"The interactive window was skipped":`No trusted input reached the page in the ${e.interaction.windowMs/1000} s window`}, and synthetic events are not counted. Observable: event.timeStamp is on the performance.now() clock, timer resolution ${e.timerResolutionMs} ms, Event Timing API ${t.eventTimingSupported?"supported":"not supported"}.`,data:n};let s={...n},a=[];for(let[i,u]of Object.entries(r)){let c=C(u.map((l)=>l.toFrameMs)),d=C(u.map((l)=>l.toPostFrameMs));if(s[i]={toFrame:c,toPostFrame:d},c)a.push(`${i} p50 ${c.p50Ms} ms to next frame (${c.samples} ${c.samples===1?"sample":"samples"})`)}if(s.eventTiming=C(t.eventTimingDurations),o<te)return{status:"requires-interaction",note:`Only ${o} trusted input ${o===1?"event":"events"} arrived, fewer than the ${te} needed to summarise. Seen: ${a.join("; ")}. Re-run and use the panel's buttons and keys.`,data:s};return{status:"measured",note:`Event timestamp to the next rAF callback: ${a.join("; ")}. Real input from the OS; no synthetic events.`,data:s}}function Be(e){let t=e.audio;if(!t.present)return{status:"unavailable",note:"window.AudioContext is undefined."};if(t.error||!t.initial)return{status:"error",note:`Creating an AudioContext failed: ${t.error??"unknown reason"}.`};let n=t.afterGesturedResume??t.afterUngesturedResume??t.initial,r={initial:t.initial,afterUngesturedResume:t.afterUngesturedResume??null,afterGesturedResume:t.afterGesturedResume??null},o=`baseLatency ${ee(n.baseLatency)}, outputLatency ${ee(n.outputLatency)} at ${n.sampleRate} Hz`,s=t.afterUngesturedResume?.state==="running"?"The context started without a user gesture":"The context stayed suspended without a user gesture";if(n.state!=="running")return{status:"requires-interaction",note:`${s}, so device latency was not observable. Reported before running: ${o}.`,data:r};if(n.baseLatency===null&&n.outputLatency===null)return{status:"unavailable",note:`${s}, but neither baseLatency nor outputLatency is implemented.`,data:r};return{status:"measured",note:`${s}. ${o}${n.outputLatency===null?" (outputLatency not implemented)":""}.`,data:r}}function _e(e){let t=e.gamepad;if(!t.present)return{status:"unavailable",note:"navigator.getGamepads is undefined."};return{status:"available",note:t.connected>0?`navigator.getGamepads() is present and reported ${t.connected} controller(s).`:"navigator.getGamepads() is present; no controller was connected, so axis and button input was not exercised.",data:{...t,secureContext:e.isSecureContext}}}function Ue(e){let t=e.fullscreen,n=t.nativeBridge.outcome==="not-attempted"||t.nativeBridge.outcome==="no-api"?"":` Craft's own window.craft.window.setFullscreen(true), which needs no gesture: ${E(t.nativeBridge)}.`,r={...t};if(!t.elementApi&&!t.webkitElementApi)return{status:"unavailable",note:`Element.requestFullscreen and webkitRequestFullscreen are both undefined, so the standard Fullscreen API engines call does nothing.${n}`,data:r};let o=`Without a gesture: ${E(t.ungestured)}.`;switch(t.gestured.outcome){case"entered":return{status:"available",note:`requestFullscreen() from a real click entered fullscreen. ${o}${n}`,data:r};case"rejected":case"timeout":return{status:"denied",note:`requestFullscreen() from a real click: ${E(t.gestured)}. ${o}${n}`,data:r};default:return{status:"requires-interaction",note:`The API exists (fullscreenEnabled ${String(t.enabled)}) but needs a real click to test. ${o}${n}`,data:r}}}function je(e){let t=e.pointerLock,n={...t};if(!t.api)return{status:"unavailable",note:"Element.requestPointerLock is undefined.",data:n};let r=`Without a gesture: ${E(t.ungestured)}.`;switch(t.gestured.outcome){case"entered":return{status:"available",note:`requestPointerLock() from a real click locked the pointer. ${r}`,data:n};case"rejected":case"timeout":return{status:"denied",note:`requestPointerLock() from a real click: ${E(t.gestured)}. ${r}`,data:n};default:return{status:"requires-interaction",note:`The API exists but needs a real click to test. ${r}`,data:n}}}function re(e,t){return{schema:Z,measuredAt:t.measuredAt,host:{...t.host,userAgent:e.userAgent},os:t.os,machine:{model:t.machine.model,cpu:t.machine.cpu,display:{...t.machine.display,devicePixelRatio:e.devicePixelRatio}},run:{idleMs:e.config.idleMs,loadMs:e.config.loadMs,cpuLoadMsPerFrame:e.config.cpuLoadMsPerFrame,triangles:e.frames.triangles,interactionWindowMs:e.interaction.windowMs,sawRealInput:e.interaction.sawRealInput},probes:{webgl2:Le(e),webgpu:Ne(e),framePacing:Oe(e,t),inputLatency:Te(e),audioLatency:Be(e),gamepad:_e(e),fullscreen:Ue(e),pointerLock:je(e)}}}import{readdirSync as ze,readFileSync as He}from"fs";import{join as Ve}from"path";import{fileURLToPath as qe}from"url";var D=qe(new URL("./results",import.meta.url));function U(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function oe(e){return`${e.host.kind==="craft"?"craft":`browser-${U(e.host.name)}`}-${U(e.os.platform)}-${U(e.os.arch)}.json`}function Xt(e=D){let t;try{t=ze(e).filter((n)=>n.endsWith(".json")).sort()}catch{return[]}return t.map((n)=>{let r=Ve(e,n);return A(JSON.parse(He(r,"utf8")),r)})}var Ge="<!-- interactive-capability:begin -->",se="<!-- interactive-capability:end -->",j="buddy desktop:probe",We={darwin:"macOS",linux:"Linux",win32:"Windows"};function ie(e){return e.host.kind==="craft"?`Craft ${e.host.version}`:`${e.host.name} ${e.host.version}`}function ae(e){return`${We[e.os.platform]??e.os.name} ${e.os.version} ${e.os.arch}, ${e.machine.cpu} (${e.machine.model})`}function Ke(e){return[...e].sort((t,n)=>{if(t.host.kind!==n.host.kind)return t.host.kind==="craft"?-1:1;return`${t.host.name} ${t.os.platform} ${t.os.arch}`.localeCompare(`${n.host.name} ${n.os.platform} ${n.os.arch}`)})}function S(e){return typeof e==="number"&&Number.isFinite(e)?e:null}function ue(e,...t){let n=e;for(let r of t){if(typeof n!=="object"||n===null)return;n=n[r]}return n}function H(e,t){let n=t.data;if(t.status!=="measured"||!n)return null;switch(e){case"framePacing":{let r=S(n.rafHz),o=S(n.displayHz),s=S(ue(n,"load","p99Ms"));if(r===null||o===null||s===null)return null;return`${r} Hz rAF on a ${o} Hz display, p99 ${s} ms under load`}case"inputLatency":{for(let r of["pointerdown","keydown","pointermove"]){let o=S(ue(n,r,"toFrame","p50Ms"));if(o!==null)return`${r} p50 ${o} ms to next frame`}return null}case"audioLatency":{let r=n.afterGesturedResume??n.afterUngesturedResume,o=S(r?.baseLatency),s=S(r?.outputLatency),a=(i)=>i===null?"n/a":`${Math.round(i*1e4)/10} ms`;return`base ${a(o)}, output ${a(s)}`}default:return null}}function Je(e,t){if(!t)return"not probed";let n=H(e,t);return n?`**${t.status}** - ${n}`:`**${t.status}**`}function z(e){return e.replaceAll("|","\\|").replaceAll(`
|
|
99
|
+
`," ")}function tn(e){let t=Ke(e),n=[Ge,"","<!-- Generated by `buddy docs:capabilities` from the records in"," storage/framework/core/desktop/src/probe/results/. Re-measure with",` \`${j} --record\`; do not edit this by hand. -->`,"","Whether a Craft window can host a game or other frame-driven content that somebody else","builds, measured by the probe harness in `@stacksjs/desktop-build`","([stacksjs/stacks#877](https://github.com/stacksjs/stacks/issues/877)). Ordinary browsers","on the same machine are measured alongside it for comparison.","","> **How current is this?** Unlike the driver tables above, these are measurements, not","> claims CI can re-check. Each column is one run on one machine, and says when, against","> which host version, on which OS and hardware. If your Craft is newer than the column,",`> the numbers describe something you are not running: re-measure with \`${j} --record\`.`,""];if(t.length===0)return n.push(`No measurement has been recorded yet. Run \`${j} --record\`.`,"",se),n.join(`
|
|
100
|
+
`);n.push(`| Probe | ${t.map((r)=>z(ie(r))).join(" | ")} |`,`|---|${t.map(()=>"---").join("|")}|`);for(let[r,o]of w)n.push(`| ${o} | ${t.map((s)=>z(Je(r,s.probes[r]))).join(" | ")} |`);n.push(`| Measured on | ${t.map((r)=>z(ae(r))).join(" | ")} |`,`| Measured at | ${t.map((r)=>r.measuredAt.slice(0,10)).join(" | ")} |`,"","- **available** / **measured** - the API exists and the probe exercised it.","- **unavailable** - the API is missing, or creating it failed.","- **requires-interaction** - needs a real click, key press or controller that the run did not have. What was still observable is in the notes; no number is invented.","- **denied** - requested from a real click and refused.","");for(let r of t){let o=r.machine.display.refreshHz===null?"unknown refresh rate":`${r.machine.display.refreshHz} Hz display`;n.push(`**${ie(r)}** - ${ae(r)}, ${o}`,"");for(let[s,a]of w){let i=r.probes[s];if(i)n.push(`- **${a}** - ${i.note}`)}n.push("")}return n.push("**Out of scope.** Stacks is not shipping a game engine, a physics engine, an audio mixer or","an asset pipeline. This section establishes what a Craft window gives an engine somebody","else wrote; it is not a plan to write one.","",se),n.join(`
|
|
101
|
+
`)}function P(e){try{let t=Bun.spawnSync(e,{stdout:"pipe",stderr:"ignore"});return t.exitCode===0?t.stdout.toString().trim():null}catch{return null}}function At(e){return e?.match(/craft version\s+(\d+\.\d+\.\d+\S*)/i)?.[1]??null}function Ft(e){if(!e)return null;try{let n=(JSON.parse(e).SPDisplaysDataType??[]).flatMap((s)=>s.spdisplays_ndrvs??[]),r=n.find((s)=>s.spdisplays_main==="spdisplays_yes")??n[0];if(!r)return null;let o=r._spdisplays_resolution??"unknown";return{name:r._name??"unknown",resolution:o,refreshHz:Q(o)}}catch{return null}}function Ct(e,t,n){let r=p.platform,o=Lt[r]??r,s=Re.release(),a,i="unknown",u=Re.cpus()[0]?.model?.trim()||"unknown",c={name:"unknown",resolution:"unknown",refreshHz:null};if(r==="darwin")o="macOS",s=P(["sw_vers","-productVersion"])??s,a=P(["sw_vers","-buildVersion"])??void 0,i=P(["sysctl","-n","hw.model"])??i,u=P(["sysctl","-n","machdep.cpu.brand_string"])??u,c=Ft(P(["system_profiler","SPDisplaysDataType","-json"]))??c;else if(r==="linux")i=P(["cat","/sys/devices/virtual/dmi/id/product_name"])??i;let d=ne(n);return{measuredAt:new Date().toISOString(),host:e==="craft"?{kind:"craft",name:"craft",version:t??"unknown"}:{kind:"browser",name:d.name,version:d.version},os:{platform:r,arch:p.arch,name:o,version:s,...a?{build:a}:{}},machine:{model:i,cpu:u,display:c}}}var Lt={darwin:"macOS",linux:"Linux",win32:"Windows"};async function Nt(){let e=Dt(Mt(import.meta.url)),t=[O(e,"page.ts"),O(e,"page.js")].find(De);if(!t)throw Error("The probe page script is missing; reinstall @stacksjs/desktop-build.");let n=await Bun.build({entrypoints:[t],target:"browser",format:"esm",minify:!0});if(!n.success)throw Error(`Bundling the probe page failed:
|
|
60
102
|
${n.logs.map(String).join(`
|
|
61
|
-
`)}`);return await n.outputs[0].text()}function
|
|
62
|
-
`)}function
|
|
63
|
-
${
|
|
64
|
-
`);let g=`${JSON.stringify(
|
|
65
|
-
`;if(t){
|
|
66
|
-
`),
|
|
103
|
+
`)}`);return await n.outputs[0].text()}function Ot(e,t){if(t===!1)return null;if(p.platform==="darwin")return typeof t==="string"?["open","-a",t,e]:["open",e];if(p.platform==="win32")return["cmd","/c","start","",e];return["xdg-open",e]}async function Tt(e){let t=e.log??((m)=>console.log(m)),n={...J,...e.page};if(e.mode==="browser")n.tryNativeFullscreen=!1;let r=null,o=null;if(e.mode==="craft"){await Promise.resolve().then(() => Pe());if(r=ve(e.craftBin),o=At(P([r,"--version"])),!o)throw Error(`Could not read a version from \`${r} --version\`; is it a Craft binary?`)}let s=K(await Nt(),n),a,i,u=new Promise((m,b)=>{a=m,i=b}),c=Bun.serve({hostname:"127.0.0.1",port:e.port??0,async fetch(m){let{pathname:b}=new URL(m.url);if(m.method==="POST"&&b==="/results")return a(await m.json()),new Response("ok");if(m.method==="POST"&&b==="/progress")return t(` page: ${await m.text()}`),new Response("ok");if(m.method==="POST"&&b==="/failed")return i(Error(`The probe page failed: ${await m.text()}`)),new Response("ok");if(b==="/")return new Response(s,{headers:{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}});return new Response("not found",{status:404})}}),d=c.url.href,l=null,g=()=>{l?.kill(),p.exit(130)};p.once("SIGINT",g),p.once("SIGTERM",g);let v=e.timeoutMs??n.idleMs+n.loadMs+n.interactionWindowMs+60000,T=setTimeout(()=>i(Error(`No results within ${Math.round(v/1000)} s. Was the window visible?`)),v);try{if(e.mode==="craft")t(`Opening ${d} in Craft ${o} (${r})`),l=Bun.spawn([r,d,"--title","Stacks interactive probe","--width","1280","--height","800"],{stdout:"ignore",stderr:"ignore"}),l.exited.then((M)=>i(Error(`Craft exited (code ${M}) before the probe finished`)));else{t(`Probe page: ${d}`);let M=Ot(d,e.open??!0);if(M)Bun.spawn(M,{stdout:"ignore",stderr:"ignore"});else t("Open that URL in the browser to measure, and keep its window visible.")}let m=await u,b=Ct(e.mode,o,m.userAgent);return A(re(m,b),"the probe result")}finally{clearTimeout(T),p.off("SIGINT",g),p.off("SIGTERM",g),l?.kill(),c.stop(!0)}}function Bt(e){let t=Math.max(...w.map(([,r])=>r.length)),n=[`${e.host.kind==="craft"?`Craft ${e.host.version}`:`${e.host.name} ${e.host.version}`} on ${e.os.name} ${e.os.version} (${e.machine.model}, ${e.machine.cpu}), display ${e.machine.display.refreshHz??"unknown"} Hz`];for(let[r,o]of w){let s=e.probes[r],a=H(r,s);n.push(` ${o.padEnd(t)} ${s.status}${a?` - ${a}`:""}`)}return n.join(`
|
|
104
|
+
`)}function k(e){let t=`--${e}=`;for(let n=0;n<p.argv.length;n++){let r=p.argv[n];if(r===`--${e}`){let o=p.argv[n+1];return o&&!o.startsWith("--")?o:!0}if(r.startsWith(t))return r.slice(t.length)}return}function N(e){let t=k(e);if(t===void 0||t===!0)return;let n=Number(t);if(!Number.isFinite(n)||n<0)throw Error(`--${e} must be a non-negative number of seconds`);return n*1000}var Ee=It(D,"../../../../../../..");async function _t(){let e=k("browser"),t=k("record")!==void 0,n=k("out"),r=k("cpu-load"),o=k("instances");if(t){if(Se(Ee,p.cwd()).startsWith("..")||!De(O(Ee,"storage/framework/core/buddy")))throw Error("--record writes into the Stacks framework repository; run it from that checkout, or use --out <file>.")}let s={},a=N("idle"),i=N("load"),u=N("interaction-window");if(a!==void 0)s.idleMs=a;if(i!==void 0)s.loadMs=i;if(u!==void 0)s.interactionWindowMs=u;if(typeof r==="string")s.cpuLoadMsPerFrame=Number(r);if(typeof o==="string")s.instances=Number(o);let c=typeof e==="string"?e:!0;if(k("no-open")!==void 0)c=!1;let d=k("craft-bin"),l=await Tt({mode:e===void 0?"craft":"browser",open:c,craftBin:typeof d==="string"?d:void 0,timeoutMs:N("timeout"),page:s});console.log(`
|
|
105
|
+
${Bt(l)}
|
|
106
|
+
`);let g=`${JSON.stringify(l,null,2)}
|
|
107
|
+
`;if(t){Et(D,{recursive:!0});let v=O(D,oe(l));$e(v,g),console.log(`Recorded ${Se(p.cwd(),v)}. Run \`buddy docs:capabilities\` to publish it.`)}if(typeof n==="string")$e(n,g),console.log(`Wrote ${n}`);if(!t&&typeof n!=="string")console.log(g)}if(import.meta.main)try{await _t()}catch(e){p.stderr.write(`${e instanceof Error?e.message:String(e)}
|
|
108
|
+
`),p.exit(1)}export{Ct as collectHostFacts,Ft as mainDisplayFromProfiler,At as parseCraftVersion,_t as run,Tt as runProbe,Bt as summarize};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/desktop-build",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.137",
|
|
6
6
|
"description": "Build and launch Craft desktop applications for Stacks",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"craft-native": ">=0.0.127"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@stacksjs/build": "0.75.
|
|
73
|
+
"@stacksjs/build": "0.75.137",
|
|
74
74
|
"better-dx": "^0.2.24",
|
|
75
|
-
"@stacksjs/server": "0.75.
|
|
75
|
+
"@stacksjs/server": "0.75.137"
|
|
76
76
|
}
|
|
77
77
|
}
|