@gjsify/cli 0.64.12-next.281 → 0.64.12

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.
@@ -39,7 +39,7 @@ import { resolveNodeModulesClosure, stageNodeModules } from '../utils/ship/node-
39
39
  import { acquireGtkRuntime } from '../utils/ship/gtk-runtime-acquire.js';
40
40
  import { discoverPayload } from '../utils/ship/discover.js';
41
41
  import { appDirFor, appDirPayload, appImageHostRequirements, appImageRuntimeNotice, buildAppImage, findPinnedRuntime, stampAppDirTimes, } from '../utils/ship/appimage.js';
42
- import { buildDmgImage, dmgVolumeDir, dmgVolumeName } from '../utils/ship/dmg.js';
42
+ import { addApplicationsLink, buildDmgImage, dmgVolumeDir, dmgVolumeName, signDmgImage } from '../utils/ship/dmg.js';
43
43
  import { assertNoDroppedFlatpakModules } from '../utils/ship/flatpak-config.js';
44
44
  import { buildFlatpakBundle } from '../utils/ship/flatpak.js';
45
45
  import { iconSizes, resolveAppIcon } from '../utils/ship/icons.js';
@@ -55,7 +55,8 @@ import { assertEntryRunsUnder } from '../utils/ship/entry-interpreter.js';
55
55
  import { scanGiRequirements } from '../utils/ship/gi-namespaces.js';
56
56
  import { assertLauncherMatchesInterpreter, assertPayloadMatchesArch, buildTimestamp, isArchIndependent, linuxInstallDependent, readPayloadFacts, } from '../utils/ship/payload.js';
57
57
  import { assertOverlayIsLicensed, planOverlay, planStage } from '../utils/ship/plan.js';
58
- import { assertHostCanSign, notarizeArtifact, stapleArtifact, resolveNotaryPlan, resolveSignPlan, signPayload, } from '../utils/ship/signing.js';
58
+ import { recordedSignature, writeReleaseRecord } from '../utils/ship/release-record.js';
59
+ import { ADHOC_IDENTITY, assertHostCanSign, notarizeArtifact, stapleArtifact, resolveNotaryPlan, resolveSignPlan, signPayload, } from '../utils/ship/signing.js';
59
60
  import { renderLauncher } from '../utils/ship/launcher.js';
60
61
  import { buildRpm } from '../utils/ship/rpm.js';
61
62
  import { resolveShipApp, resolveShipBundle, resolveShipKind, resolveShipSettings, } from '../utils/ship/settings.js';
@@ -1261,6 +1262,7 @@ async function packOne(input) {
1261
1262
  // mounts showing `Contents/` — a window with no application in it.
1262
1263
  const volumeDir = dmgVolumeDir(outRoot);
1263
1264
  writePayload(volumeDir, payload, '');
1265
+ addApplicationsLink(volumeDir);
1264
1266
  await buildDmgImage({
1265
1267
  volumeName: dmgVolumeName(settings),
1266
1268
  sourceDir: volumeDir,
@@ -1268,6 +1270,17 @@ async function packOne(input) {
1268
1270
  workDir: dirname(volumeDir),
1269
1271
  verbose: input.verbose,
1270
1272
  });
1273
+ // The IMAGE's own signature, before the notarisation below submits
1274
+ // it and the staple writes into it — see `utils/ship/dmg.ts`.
1275
+ if (input.sign.kind === 'sign') {
1276
+ await signDmgImage({
1277
+ identity: input.sign.identity,
1278
+ file: target,
1279
+ adhoc: input.sign.identity === ADHOC_IDENTITY,
1280
+ workDir: dirname(volumeDir),
1281
+ verbose: input.verbose,
1282
+ });
1283
+ }
1271
1284
  break;
1272
1285
  }
1273
1286
  case 'macos-app-zip':
@@ -1339,6 +1352,8 @@ async function packOne(input) {
1339
1352
  // installer, so a `<App>.app` has no submittable form of its own; on darwin
1340
1353
  // the zip beside it is the one that goes. Refusing here would make
1341
1354
  // `--notarize` unusable with the default format set, which is both rows.
1355
+ let notarized = false;
1356
+ let stapled = false;
1342
1357
  if (input.notarize.kind === 'notarize') {
1343
1358
  if (format.artifactKind === 'directory') {
1344
1359
  console.warn(`${LOG} ${format.id} is a directory, and notarytool submits an archive or an installer — ` +
@@ -1351,6 +1366,7 @@ async function packOne(input) {
1351
1366
  verbose: input.verbose,
1352
1367
  log: (line) => console.log(`${LOG} ${line}`),
1353
1368
  });
1369
+ notarized = true;
1354
1370
  // AND THEN THE TICKET, on the artifacts that can hold one. Submitting
1355
1371
  // and stapling are two questions with two answers, and the `.zip` is
1356
1372
  // where they come apart: the notary service accepts it and `stapler`
@@ -1365,6 +1381,7 @@ async function packOne(input) {
1365
1381
  verbose: input.verbose,
1366
1382
  log: (line) => console.log(`${LOG} ${line}`),
1367
1383
  });
1384
+ stapled = true;
1368
1385
  }
1369
1386
  else {
1370
1387
  console.warn(`${LOG} ${format.id} is notarised and NOT stapled: \`stapler\` takes a UDIF image, a ` +
@@ -1374,6 +1391,22 @@ async function packOne(input) {
1374
1391
  }
1375
1392
  }
1376
1393
  }
1394
+ // THE RELEASE RECORD, after every step that writes into the file (ADR 0124).
1395
+ // A directory is never a release asset; the container beside it is.
1396
+ if (format.artifactKind === 'file') {
1397
+ const record = writeReleaseRecord({
1398
+ file: target,
1399
+ format: format.id,
1400
+ os: format.layoutOs,
1401
+ arch: isArchIndependent(payload) ? 'noarch' : input.arch,
1402
+ signature: recordedSignature(input.sign.kind === 'sign' ? input.sign.identity : undefined, ADHOC_IDENTITY),
1403
+ notarized,
1404
+ stapled,
1405
+ generator: `@gjsify/cli ${cliVersion()}`,
1406
+ });
1407
+ if (input.verbose)
1408
+ console.log(`${LOG} release record: ${record}`);
1409
+ }
1377
1410
  return { format: format.id, path: target, size };
1378
1411
  }
1379
1412
  function printArtifacts(base, artifacts) {
@@ -22,7 +22,7 @@ export declare function dmgVolumeName(settings: PackSettings): string;
22
22
  export interface DmgCreateInput {
23
23
  /** The volume label — see {@link dmgVolumeName}. */
24
24
  volumeName: string;
25
- /** The directory whose CONTENTS become the volume root; it holds `<App>.app` and nothing else. */
25
+ /** The directory whose CONTENTS become the volume root: `<App>.app` and the `Applications` link. */
26
26
  sourceDir: string;
27
27
  /** Absolute path the finished `.dmg` is written to. */
28
28
  target: string;
@@ -63,9 +63,57 @@ export declare function buildDmgImage(input: DmgPackInput): Promise<void>;
63
63
  * Where the volume is assembled before `hdiutil` turns it into an image.
64
64
  *
65
65
  * A directory of its own under the run's output root, never `out/` itself: the
66
- * volume root holds the `.app` and NOTHING ELSE, because `-srcfolder` copies
66
+ * volume root holds the `.app` and its drop target and NOTHING ELSE, because `-srcfolder` copies
67
67
  * whatever it finds — a stray sibling would ride into the image, and the reader
68
68
  * that would catch it is the same listing comparison that would then have to be
69
69
  * relaxed to allow it.
70
70
  */
71
71
  export declare function dmgVolumeDir(outRoot: string): string;
72
+ /** The drop target's name in the volume root, and where it points. */
73
+ export declare const DMG_APPLICATIONS_LINK: {
74
+ readonly name: "Applications";
75
+ readonly target: "/Applications";
76
+ };
77
+ /**
78
+ * Put the drag-here link into a volume `writePayload` has just filled.
79
+ *
80
+ * AFTER `writePayload`, never before: that call wipes the directory it writes,
81
+ * so it is also what keeps a previous run's link from making this one fail
82
+ * with `EEXIST`. An ABSOLUTE target, because the link is resolved on the
83
+ * user's machine where `/Applications` is the only path that means anything —
84
+ * the Finder shows it as "Applications" with the folder's own icon.
85
+ */
86
+ export declare function addApplicationsLink(volumeDir: string): void;
87
+ /** The tool that signs the image. The bundle's signer uses the same one. */
88
+ export declare const DMG_SIGN_TOOL = "codesign";
89
+ export interface DmgSignInput {
90
+ /** A `codesign` identity, or `-` for ad-hoc. */
91
+ identity: string;
92
+ /** The finished `.dmg`. */
93
+ file: string;
94
+ /** `identity` is the ad-hoc `-`. */
95
+ adhoc: boolean;
96
+ }
97
+ /**
98
+ * The `codesign` invocation for the IMAGE, as data.
99
+ *
100
+ * NOT the darwin signer's argv (`SIGNERS.darwin.args`), and the two differences
101
+ * are the point. No `--options runtime`: the hardened runtime is a property of
102
+ * code that executes, and a disk image does not. No entitlements, for the same
103
+ * reason. What stays is `--force` — `-ov` rewrites the file, but a caller may
104
+ * hand in one that is already signed — and `--timestamp` on a real identity
105
+ * only, for the reason that row gives: an ad-hoc signature has no certificate
106
+ * to countersign, and `codesign(1)` documents a timestamp failure as fatal.
107
+ */
108
+ export declare function codesignDmgArgs(input: DmgSignInput): string[];
109
+ /**
110
+ * Sign the image at `input.file`.
111
+ *
112
+ * Ad-hoc is signed too. It earns the image nothing with Gatekeeper, but it is
113
+ * what CI runs, and a branch only a release with a real certificate reaches is
114
+ * a branch that breaks on release day.
115
+ */
116
+ export declare function signDmgImage(input: DmgSignInput & {
117
+ verbose: boolean;
118
+ workDir: string;
119
+ }): Promise<void>;
@@ -39,13 +39,25 @@
39
39
  // ubuntu-24.04 ships 7-Zip 23.01, not this workstation's 26.02. zlib is the
40
40
  // method its `Dmg` handler has read since long before either.
41
41
  //
42
- // WHAT THIS PACKER DOES NOT PUT IN THE VOLUME: an `/Applications` symlink, the
43
- // drag-here convention. `writePayload` writes regular files and nothing else —
44
- // which is a property `tests/e2e/ship` and the `ship-stage` job both assert, and
45
- // a symlink is also what `actions/upload-artifact` silently dereferences. The
46
- // artifact is still draggable out of the mounted volume; what it lacks is the
47
- // arrow. Unblocker: a staged-symlink concept with a reader that can see one, at
48
- // which point the link and its oracle land together.
42
+ // THE VOLUME CARRIES ONE SYMLINK, `Applications → /Applications`, the drag-here
43
+ // convention: without it the mounted window shows the bundle and nothing to drop
44
+ // it on. It is NOT staged. `writePayload` writes regular files and nothing else —
45
+ // a property `tests/e2e/ship` and the `ship-stage` job both assert — and a staged
46
+ // symlink would be dereferenced by `actions/upload-artifact` on its way to the
47
+ // packing host. The volume directory is assembled ON that host, after the stage
48
+ // has arrived, so neither objection reaches it: the link is written here, next to
49
+ // the `hdiutil` call that consumes it. Its reader is `verify-dmg.py`, which admits
50
+ // exactly this one link with exactly this target and refuses any other.
51
+ //
52
+ // THE IMAGE ITSELF IS SIGNED, and that is a separate signature from the bundle's.
53
+ // Measured on macOS 27 with a Developer ID identity (2026-10-29): a notarised and
54
+ // stapled `.dmg` whose container was never passed to `codesign` reads "code object
55
+ // is not signed at all" under `codesign -dv`, and `spctl -a -t open
56
+ // --context context:primary-signature` rejects it with "no usable signature" —
57
+ // while the `.app` mounted from it is "accepted, source=Notarized Developer ID".
58
+ // The order is fixed by `stapler(1)`: a later `codesign` invalidates a stapled
59
+ // ticket, so the image is signed here, before `ship` notarises and staples it.
60
+ import { symlinkSync } from 'node:fs';
49
61
  import { join } from 'node:path';
50
62
  import { describeExit, spawnToCompletion } from '../spawn.js';
51
63
  /** The tool this packer execs. One spelling, read by this module AND by the format row. */
@@ -171,7 +183,7 @@ export async function buildDmgImage(input) {
171
183
  * Where the volume is assembled before `hdiutil` turns it into an image.
172
184
  *
173
185
  * A directory of its own under the run's output root, never `out/` itself: the
174
- * volume root holds the `.app` and NOTHING ELSE, because `-srcfolder` copies
186
+ * volume root holds the `.app` and its drop target and NOTHING ELSE, because `-srcfolder` copies
175
187
  * whatever it finds — a stray sibling would ride into the image, and the reader
176
188
  * that would catch it is the same listing comparison that would then have to be
177
189
  * relaxed to allow it.
@@ -179,3 +191,55 @@ export async function buildDmgImage(input) {
179
191
  export function dmgVolumeDir(outRoot) {
180
192
  return join(outRoot, 'dmg', 'volume');
181
193
  }
194
+ /** The drop target's name in the volume root, and where it points. */
195
+ export const DMG_APPLICATIONS_LINK = { name: 'Applications', target: '/Applications' };
196
+ /**
197
+ * Put the drag-here link into a volume `writePayload` has just filled.
198
+ *
199
+ * AFTER `writePayload`, never before: that call wipes the directory it writes,
200
+ * so it is also what keeps a previous run's link from making this one fail
201
+ * with `EEXIST`. An ABSOLUTE target, because the link is resolved on the
202
+ * user's machine where `/Applications` is the only path that means anything —
203
+ * the Finder shows it as "Applications" with the folder's own icon.
204
+ */
205
+ export function addApplicationsLink(volumeDir) {
206
+ symlinkSync(DMG_APPLICATIONS_LINK.target, join(volumeDir, DMG_APPLICATIONS_LINK.name));
207
+ }
208
+ /** The tool that signs the image. The bundle's signer uses the same one. */
209
+ export const DMG_SIGN_TOOL = 'codesign';
210
+ /**
211
+ * The `codesign` invocation for the IMAGE, as data.
212
+ *
213
+ * NOT the darwin signer's argv (`SIGNERS.darwin.args`), and the two differences
214
+ * are the point. No `--options runtime`: the hardened runtime is a property of
215
+ * code that executes, and a disk image does not. No entitlements, for the same
216
+ * reason. What stays is `--force` — `-ov` rewrites the file, but a caller may
217
+ * hand in one that is already signed — and `--timestamp` on a real identity
218
+ * only, for the reason that row gives: an ad-hoc signature has no certificate
219
+ * to countersign, and `codesign(1)` documents a timestamp failure as fatal.
220
+ */
221
+ export function codesignDmgArgs(input) {
222
+ return ['--force', '--sign', input.identity, ...(input.adhoc ? [] : ['--timestamp']), input.file];
223
+ }
224
+ /**
225
+ * Sign the image at `input.file`.
226
+ *
227
+ * Ad-hoc is signed too. It earns the image nothing with Gatekeeper, but it is
228
+ * what CI runs, and a branch only a release with a real certificate reaches is
229
+ * a branch that breaks on release day.
230
+ */
231
+ export async function signDmgImage(input) {
232
+ const args = codesignDmgArgs(input);
233
+ if (input.verbose)
234
+ console.log(`[gjsify ship] ${DMG_SIGN_TOOL} ${args.join(' ')}`);
235
+ const result = await spawnToCompletion(DMG_SIGN_TOOL, args, {
236
+ completion: 'return',
237
+ cwd: input.workDir,
238
+ stdio: 'inherit',
239
+ notFound: () => new Error(`gjsify ship: ${DMG_SIGN_TOOL} is not on PATH, so the image cannot be signed. Install the ` +
240
+ 'Xcode Command Line Tools (`xcode-select --install`).'),
241
+ });
242
+ if (result.code !== 0) {
243
+ throw new Error(`gjsify ship: ${DMG_SIGN_TOOL} failed on the image with ${describeExit(result)}.`);
244
+ }
245
+ }
@@ -0,0 +1,47 @@
1
+ import type { FormatId, HostOs } from './types.js';
2
+ /** The suffix a record's name adds to its artifact's. The site matches on it. */
3
+ export declare const RELEASE_RECORD_SUFFIX = ".gjsify-release.json";
4
+ export declare const RELEASE_RECORD_SCHEMA = 1;
5
+ export type RecordedSignature = {
6
+ kind: 'none';
7
+ } | {
8
+ kind: 'adhoc';
9
+ } | {
10
+ kind: 'identity';
11
+ identity: string;
12
+ };
13
+ export interface ReleaseRecord {
14
+ schema: typeof RELEASE_RECORD_SCHEMA;
15
+ /** The artifact's file name, which is also its release asset name. */
16
+ artifact: string;
17
+ /** Lower-case hex SHA-256 of the artifact as written. */
18
+ sha256: string;
19
+ size: number;
20
+ format: FormatId;
21
+ os: HostOs;
22
+ /** `process.arch` spelling, or `noarch` for a payload with no native code. */
23
+ arch: string;
24
+ signature: RecordedSignature;
25
+ notarized: boolean;
26
+ stapled: boolean;
27
+ /** `@gjsify/cli <version>` — who wrote this record. */
28
+ generator: string;
29
+ }
30
+ export interface ReleaseRecordInput {
31
+ /** Absolute path of the finished artifact file. */
32
+ file: string;
33
+ format: FormatId;
34
+ os: HostOs;
35
+ arch: string;
36
+ signature: RecordedSignature;
37
+ notarized: boolean;
38
+ stapled: boolean;
39
+ generator: string;
40
+ }
41
+ export declare function releaseRecordPath(artifact: string): string;
42
+ /** Map a run's signing identity to what the record says; `-` is ad-hoc. */
43
+ export declare function recordedSignature(identity: string | undefined, adhocIdentity: string): RecordedSignature;
44
+ /** Build the record from the bytes at `input.file`. Pure apart from reading that file. */
45
+ export declare function buildReleaseRecord(input: ReleaseRecordInput): ReleaseRecord;
46
+ /** Write the record beside its artifact and return the record's path. */
47
+ export declare function writeReleaseRecord(input: ReleaseRecordInput): string;
@@ -0,0 +1,55 @@
1
+ // The release record — ADR 0124.
2
+ //
3
+ // ONE FILE BESIDE EVERY FILE ARTIFACT, `<artifact>.gjsify-release.json`, saying
4
+ // what this run did to it: which signature it carries, whether Apple accepted
5
+ // it, whether a ticket was stapled. `@gjsify/site` reads it to decide whether a
6
+ // download needs the "how to open an unsigned app" hint.
7
+ //
8
+ // WRITTEN LAST, from outcomes and not from flags. The hash is taken from the
9
+ // bytes on disk after `stapler` wrote into them, because a hash from before
10
+ // the staple describes a file nobody downloads. A step that fails throws
11
+ // before this runs, so a record never claims a step that did not finish.
12
+ //
13
+ // Per artifact rather than per run or per release: a release is assembled by
14
+ // several jobs on several hosts, and a file named by its artifact cannot
15
+ // collide with another job's.
16
+ import { createHash } from 'node:crypto';
17
+ import { readFileSync, writeFileSync } from 'node:fs';
18
+ import { basename } from 'node:path';
19
+ /** The suffix a record's name adds to its artifact's. The site matches on it. */
20
+ export const RELEASE_RECORD_SUFFIX = '.gjsify-release.json';
21
+ export const RELEASE_RECORD_SCHEMA = 1;
22
+ export function releaseRecordPath(artifact) {
23
+ return `${artifact}${RELEASE_RECORD_SUFFIX}`;
24
+ }
25
+ /** Map a run's signing identity to what the record says; `-` is ad-hoc. */
26
+ export function recordedSignature(identity, adhocIdentity) {
27
+ if (identity === undefined)
28
+ return { kind: 'none' };
29
+ if (identity === adhocIdentity)
30
+ return { kind: 'adhoc' };
31
+ return { kind: 'identity', identity };
32
+ }
33
+ /** Build the record from the bytes at `input.file`. Pure apart from reading that file. */
34
+ export function buildReleaseRecord(input) {
35
+ const bytes = readFileSync(input.file);
36
+ return {
37
+ schema: RELEASE_RECORD_SCHEMA,
38
+ artifact: basename(input.file),
39
+ sha256: createHash('sha256').update(bytes).digest('hex'),
40
+ size: bytes.length,
41
+ format: input.format,
42
+ os: input.os,
43
+ arch: input.arch,
44
+ signature: input.signature,
45
+ notarized: input.notarized,
46
+ stapled: input.stapled,
47
+ generator: input.generator,
48
+ };
49
+ }
50
+ /** Write the record beside its artifact and return the record's path. */
51
+ export function writeReleaseRecord(input) {
52
+ const path = releaseRecordPath(input.file);
53
+ writeFileSync(path, `${JSON.stringify(buildReleaseRecord(input), null, 4)}\n`);
54
+ return path;
55
+ }
@@ -266,12 +266,12 @@ export declare function resolveNotaryPlan(input: {
266
266
  /**
267
267
  * Submit one written artifact for notarisation.
268
268
  *
269
- * ⚠️ UNVERIFIED END TO END. Everything up to the `spawnToCompletion` is covered
270
- * — the argv by `signing.spec.ts`, the guard and both refusals by
271
- * `tests/e2e/ship-signing` — and the invocation itself has never run: it needs
272
- * an Apple Developer account, and § A17's whole point is that M6's proof does
273
- * not. Whoever first runs this on a real credential should record what it did
274
- * beside § A15, which is where the question was left open.
269
+ * VERIFIED BY HAND, NOT IN CI. The argv is covered by `signing.spec.ts`, the
270
+ * guard and both refusals by `tests/e2e/ship-signing`; the invocation needs an
271
+ * Apple Developer account, which § A17 keeps out of CI. It has been run with a
272
+ * real Developer ID and keychain profile on 2026-10-29 (ADR 0024, amendment of
273
+ * that date): the `.dmg` and the `.zip` of a GTK application both came back
274
+ * `Accepted`.
275
275
  */
276
276
  export declare function notarizeArtifact(input: {
277
277
  plan: Extract<NotaryPlan, {
@@ -298,9 +298,10 @@ export declare function notarizeArtifact(input: {
298
298
  * instead — which is a fact about the CONTAINER and therefore lives on the format
299
299
  * (`canCarryTicket`), not in a branch here.
300
300
  *
301
- * ⚠️ UNVERIFIED END TO END, for the same reason `notarizeArtifact` is: stapling
302
- * needs a real notarisation, which needs an Apple account (ADR 0024 § A15, § A17).
303
- * The argv is unit-tested and the invocation has never run in this repository.
301
+ * VERIFIED BY HAND, for the same reason as `notarizeArtifact`: stapling needs a
302
+ * real notarisation (ADR 0024 § A15, § A17). Run on 2026-10-29 against a signed
303
+ * `.dmg` — "The staple and validate action worked!", and `spctl` accepts the
304
+ * image afterwards. The argv is unit-tested.
304
305
  *
305
306
  * `stapler(1)` also states the constraint that decides the ORDER: *"Stapling does
306
307
  * not invalidate the code signature and must be run after an executable or archive
@@ -461,12 +461,12 @@ export function resolveNotaryPlan(input) {
461
461
  /**
462
462
  * Submit one written artifact for notarisation.
463
463
  *
464
- * ⚠️ UNVERIFIED END TO END. Everything up to the `spawnToCompletion` is covered
465
- * — the argv by `signing.spec.ts`, the guard and both refusals by
466
- * `tests/e2e/ship-signing` — and the invocation itself has never run: it needs
467
- * an Apple Developer account, and § A17's whole point is that M6's proof does
468
- * not. Whoever first runs this on a real credential should record what it did
469
- * beside § A15, which is where the question was left open.
464
+ * VERIFIED BY HAND, NOT IN CI. The argv is covered by `signing.spec.ts`, the
465
+ * guard and both refusals by `tests/e2e/ship-signing`; the invocation needs an
466
+ * Apple Developer account, which § A17 keeps out of CI. It has been run with a
467
+ * real Developer ID and keychain profile on 2026-10-29 (ADR 0024, amendment of
468
+ * that date): the `.dmg` and the `.zip` of a GTK application both came back
469
+ * `Accepted`.
470
470
  */
471
471
  export async function notarizeArtifact(input) {
472
472
  const { notary, credential } = input.plan;
@@ -504,9 +504,10 @@ export async function notarizeArtifact(input) {
504
504
  * instead — which is a fact about the CONTAINER and therefore lives on the format
505
505
  * (`canCarryTicket`), not in a branch here.
506
506
  *
507
- * ⚠️ UNVERIFIED END TO END, for the same reason `notarizeArtifact` is: stapling
508
- * needs a real notarisation, which needs an Apple account (ADR 0024 § A15, § A17).
509
- * The argv is unit-tested and the invocation has never run in this repository.
507
+ * VERIFIED BY HAND, for the same reason as `notarizeArtifact`: stapling needs a
508
+ * real notarisation (ADR 0024 § A15, § A17). Run on 2026-10-29 against a signed
509
+ * `.dmg` — "The staple and validate action worked!", and `spctl` accepts the
510
+ * image afterwards. The argv is unit-tested.
510
511
  *
511
512
  * `stapler(1)` also states the constraint that decides the ORDER: *"Stapling does
512
513
  * not invalidate the code signature and must be run after an executable or archive
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.64.12-next.281",
3
+ "version": "0.64.12",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,22 +142,22 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/blueprint": "0.64.12-next.281",
146
- "@gjsify/buffer": "0.64.12-next.281",
147
- "@gjsify/create-app": "0.64.12-next.281",
148
- "@gjsify/node-globals": "0.64.12-next.281",
149
- "@gjsify/node-polyfills": "0.64.12-next.281",
150
- "@gjsify/npm-registry": "0.64.12-next.281",
151
- "@gjsify/resolve-npm": "0.64.12-next.281",
152
- "@gjsify/rolldown-plugin-gjsify": "0.64.12-next.281",
153
- "@gjsify/vite-plugin-blueprint": "0.64.12-next.281",
154
- "@gjsify/rolldown-plugin-pnp": "0.64.12-next.281",
155
- "@gjsify/semver": "0.64.12-next.281",
156
- "@gjsify/tar": "0.64.12-next.281",
157
- "@gjsify/tsc": "0.64.12-next.281",
158
- "@gjsify/utils": "0.64.12-next.281",
159
- "@gjsify/web-polyfills": "0.64.12-next.281",
160
- "@gjsify/workspace": "0.64.12-next.281",
145
+ "@gjsify/blueprint": "^0.64.12",
146
+ "@gjsify/buffer": "^0.64.12",
147
+ "@gjsify/create-app": "^0.64.12",
148
+ "@gjsify/node-globals": "^0.64.12",
149
+ "@gjsify/node-polyfills": "^0.64.12",
150
+ "@gjsify/npm-registry": "^0.64.12",
151
+ "@gjsify/resolve-npm": "^0.64.12",
152
+ "@gjsify/rolldown-plugin-gjsify": "^0.64.12",
153
+ "@gjsify/vite-plugin-blueprint": "^0.64.12",
154
+ "@gjsify/rolldown-plugin-pnp": "^0.64.12",
155
+ "@gjsify/semver": "^0.64.12",
156
+ "@gjsify/tar": "^0.64.12",
157
+ "@gjsify/tsc": "^0.64.12",
158
+ "@gjsify/utils": "^0.64.12",
159
+ "@gjsify/web-polyfills": "^0.64.12",
160
+ "@gjsify/workspace": "^0.64.12",
161
161
  "acorn": "^8.17.0",
162
162
  "cosmiconfig": "^9.0.2",
163
163
  "get-tsconfig": "^4.14.0",
@@ -166,13 +166,13 @@
166
166
  "yargs": "^18.0.0"
167
167
  },
168
168
  "devDependencies": {
169
- "@gjsify/unit": "0.64.12-next.281",
169
+ "@gjsify/unit": "^0.64.12",
170
170
  "@types/yargs": "^17.0.35",
171
171
  "typescript": "^6.0.3"
172
172
  },
173
173
  "peerDependencies": {
174
- "@gjsify/oxfmt-native": "0.64.12-next.281",
175
- "@gjsify/rolldown-native": "0.64.12-next.281"
174
+ "@gjsify/oxfmt-native": "^0.64.12",
175
+ "@gjsify/rolldown-native": "^0.64.12"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "@gjsify/oxfmt-native": {
@@ -191,6 +191,5 @@
191
191
  "bugs": {
192
192
  "url": "https://github.com/gjsify/gjsify/issues"
193
193
  },
194
- "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/cli#readme",
195
- "gitHead": "c4d2e266c0d9849860b9828c14ebfe37b6d493b8"
194
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/cli#readme"
196
195
  }