@rightkit/release 0.2.132 → 0.2.133
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/apple-profile-renew.mjs +220 -0
- package/artifact-storage.mjs +344 -0
- package/build-release.mjs +39 -18
- package/cli/right-release.mjs +9 -3
- package/create-mac-updater.mjs +8 -15
- package/development-signing.mjs +193 -0
- package/development.mjs +15 -13
- package/installer-storage-doctor.mjs +120 -0
- package/macos-notarization.mjs +72 -0
- package/macos-signing-session.mjs +150 -0
- package/mirror-root-artifact.mjs +46 -11
- package/model-promote.mjs +32 -5
- package/notarize-macos.mjs +10 -32
- package/ownership-key-command.mjs +3 -1
- package/ownership-report.mjs +15 -5
- package/package.json +1 -1
- package/publish-update.mjs +14 -6
- package/release.mjs +42 -18
- package/rightkit-signing-agent.mjs +253 -0
- package/rightkit-versions.json +33 -24
- package/runtime-artifact-manifest.mjs +5 -1
- package/sign-macos.mjs +32 -4
- package/sign-updater.mjs +8 -13
- package/signing-agent-client.mjs +85 -0
- package/signing-agent-command.mjs +21 -0
- package/signing-agent-install.mjs +100 -0
- package/signing-receipts.mjs +10 -0
- package/upload-release.mjs +4 -0
package/build-release.mjs
CHANGED
|
@@ -27,6 +27,8 @@ import { resolveNativeCargoLayout } from "./native-cargo-layout.mjs";
|
|
|
27
27
|
import { commandOutputPortable, releaseEnvironment, resolveReleaseLayout, runBuildStateMachine, verifySealedRelease, watchProgress } from "./release-state.mjs";
|
|
28
28
|
import { acquireCacheLease, acquireSuiteBuildSlot, applyCachePrune, assertWriteVolumeFloors, inspectCache, inspectWriteVolumes, markCacheEntrySuccessful, planCachePrune, readCachePolicy, resolveCacheLayout, resolveSharedCacheIdentity, resolveSharedCacheRoot } from "./cache-policy.mjs";
|
|
29
29
|
import { createTargetBridge } from "./target-bridge.mjs";
|
|
30
|
+
import { signingReceiptPath } from "./signing-receipts.mjs";
|
|
31
|
+
import { validateNotarizationReceipt } from "./macos-notarization.mjs";
|
|
30
32
|
import { brokerManagedHost } from "./cargo-contract.mjs";
|
|
31
33
|
import { resolveReleaseCacheMode } from "./release-cache-mode.mjs";
|
|
32
34
|
import { resolveTargetRoot } from "./cargo-target.mjs";
|
|
@@ -36,14 +38,14 @@ import { assertCleanSource } from "./source-gate.mjs";
|
|
|
36
38
|
import { acquireHeavyWorkSlot, heavyCommandEnvironment, summarizePreBrokerWaits, terminateProcessTree } from "./heavy-command.mjs";
|
|
37
39
|
import { processGroupCpuSeconds } from "./process-liveness.mjs";
|
|
38
40
|
import { cancelAndReapManagedRequest, firstManagedRequestId, releaseProgressLimits } from "./progress-control.mjs";
|
|
41
|
+
import { beginArtifactLease, completeArtifactLease, materializeArtifacts, retainArtifacts, resolveArtifactDir, resolveManagedRoot } from "./artifact-storage.mjs";
|
|
39
42
|
|
|
40
43
|
// Fingerprint of the pipeline code that can change the bytes we ship or the way
|
|
41
44
|
// they are signed — deliberately NOT the package version, which moves for docs
|
|
42
45
|
// and test-only edits and would force a cold Rust rebuild of every app each time.
|
|
43
|
-
//
|
|
44
|
-
// outlive.
|
|
46
|
+
// Cached target directories must not outlive changes to these signing helpers.
|
|
45
47
|
|
|
46
|
-
const PIPELINE_FINGERPRINT_SOURCES = ["release.mjs", "sign-windows.mjs", "sign-macos.mjs", "notarize-macos.mjs", "tauri-bundle-marker.mjs", "nsis-payload.mjs"];
|
|
48
|
+
const PIPELINE_FINGERPRINT_SOURCES = ["release.mjs", "sign-windows.mjs", "sign-macos.mjs", "macos-signing-session.mjs", "notarize-macos.mjs", "macos-notarization.mjs", "signing-receipts.mjs", "tauri-bundle-marker.mjs", "nsis-payload.mjs"];
|
|
47
49
|
const PIPELINE_FINGERPRINT = createHash("sha256")
|
|
48
50
|
.update(PIPELINE_FINGERPRINT_SOURCES.map((file) => `${file}:${createHash("sha256").update(readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), file))).digest("hex")}`).join("\n"))
|
|
49
51
|
.digest("hex")
|
|
@@ -170,7 +172,9 @@ try {
|
|
|
170
172
|
if (dirtyInputs.length > 0) fail(`dirty files can change the packaged app; resolve them before release:\n${dirtyInputs.map((file) => `- ${file}`).join("\n")}`);
|
|
171
173
|
const toolVersions = collectToolVersions(config.packageManager ?? "pnpm");
|
|
172
174
|
const inputHashes = hashInputs(requiredInputs);
|
|
173
|
-
const
|
|
175
|
+
const releaseId = `${config.app}-${config.version}-${shortCommit}`;
|
|
176
|
+
const platformDir = platform === "win" ? "windows" : "mac";
|
|
177
|
+
const stateRoot = path.join(vaultRoot, "state", releaseId, platformDir);
|
|
174
178
|
const signingIdentity = platform === "win" ? {
|
|
175
179
|
contract: target.signingContract ?? "<missing>",
|
|
176
180
|
// Pipeline code participates in cache identity. Without it, a fix to the
|
|
@@ -184,10 +188,7 @@ try {
|
|
|
184
188
|
// windows-<phase>.json name is only the fallback. Hardcoding the default
|
|
185
189
|
// here made the seal read a path the signer never wrote whenever a config
|
|
186
190
|
// (e.g. an architecture-qualified receipt) overrode it.
|
|
187
|
-
receiptInputs: ["raw-exe", "installer", "embedding"].map((phase) => {
|
|
188
|
-
const configured = phase === "raw-exe" ? target.sign?.receipt : phase === "installer" ? target.sign?.installerReceipt : undefined;
|
|
189
|
-
return configured ? path.resolve(appRoot, configured) : path.join(receiptRoot, `windows-${phase}.json`);
|
|
190
|
-
}),
|
|
191
|
+
receiptInputs: ["raw-exe", "installer", "embedding"].map((phase) => signingReceiptPath({ appRoot, target, platform, phase, stateRoot })),
|
|
191
192
|
} : platform === "mac" ? {
|
|
192
193
|
contract: target.signingContract ?? "<missing>",
|
|
193
194
|
pipeline: PIPELINE_FINGERPRINT,
|
|
@@ -195,10 +196,7 @@ try {
|
|
|
195
196
|
// Mac portable releases produce one Developer ID receipt before packaging
|
|
196
197
|
// and one Accepted notarization receipt after. Both must be part of cache
|
|
197
198
|
// identity and seal evidence, including configured receipt overrides.
|
|
198
|
-
receiptInputs: [
|
|
199
|
-
target.sign?.receipt ? path.resolve(appRoot, target.sign.receipt) : path.join(receiptRoot, "macos-signing.json"),
|
|
200
|
-
target.notarize?.receipt ? path.resolve(appRoot, target.notarize.receipt) : path.join(receiptRoot, "macos-notarization.json"),
|
|
201
|
-
],
|
|
199
|
+
receiptInputs: ["signing", "notarization"].map((phase) => signingReceiptPath({ appRoot, target, platform, phase, stateRoot })),
|
|
202
200
|
} : null;
|
|
203
201
|
if (signingIdentity) inputHashes[".right-release/signing-identity.json"] = hashFileText(JSON.stringify(signingIdentity));
|
|
204
202
|
const cargoLock = nativeLayout.lockPath;
|
|
@@ -259,6 +257,8 @@ try {
|
|
|
259
257
|
...(pathPrefix ? { PATH: `${pathPrefix}${path.delimiter}${process.env.PATH ?? ""}` } : {}),
|
|
260
258
|
RIGHT_RELEASE_REPO_ROOT: layout.repoRoot,
|
|
261
259
|
RIGHT_RELEASE_APP_ROOT: layout.appRoot,
|
|
260
|
+
RIGHTKIT_MANAGED_ROOT: resolveManagedRoot({ env: process.env }),
|
|
261
|
+
RIGHT_RELEASE_ARTIFACT_DIR: resolveArtifactDir({ app: config.app, version: config.version, managedRoot: resolveManagedRoot({ env: process.env }) }),
|
|
262
262
|
};
|
|
263
263
|
if (managedCargoTarget) {
|
|
264
264
|
for (const name of ["CARGO_HOME", "CARGO_TARGET_DIR", "RUSTC_WRAPPER", "SCCACHE_DIR", "SCCACHE_BASEDIRS", "SCCACHE_CACHE_SIZE", "RUSTFLAGS"]) {
|
|
@@ -277,10 +277,12 @@ try {
|
|
|
277
277
|
const preBrokerWaits = summarizePreBrokerWaits({ heavySlot, suiteSlot, heavyOwnerInherited: process.env.RIGHTSUITE_HEAVY_WORK_OWNER === "1" });
|
|
278
278
|
console.error(`[right-release] pre-broker waits: ${preBrokerWaits.summary}`);
|
|
279
279
|
throwIfInterrupted();
|
|
280
|
-
const
|
|
281
|
-
const
|
|
280
|
+
const managedRoot = resolveManagedRoot({ env: process.env });
|
|
281
|
+
const managedArtifactDir = resolveArtifactDir({ app: config.app, version: config.version, managedRoot });
|
|
282
|
+
beginArtifactLease({ app: config.app, version: config.version, managedRoot });
|
|
283
|
+
env.RIGHTKIT_MANAGED_ROOT = managedRoot;
|
|
284
|
+
env.RIGHT_RELEASE_ARTIFACT_DIR = managedArtifactDir;
|
|
282
285
|
const buildRoot = managedCargoTarget ? null : path.join(vaultRoot, "build", config.app, config.version, shortCommit, platformDir);
|
|
283
|
-
const stateRoot = path.join(vaultRoot, "state", releaseId, platformDir);
|
|
284
286
|
env.RIGHT_RELEASE_STATE_ROOT = stateRoot;
|
|
285
287
|
const lockPayload = {
|
|
286
288
|
schema: 1,
|
|
@@ -384,7 +386,7 @@ try {
|
|
|
384
386
|
});
|
|
385
387
|
},
|
|
386
388
|
seal: async ({ sealedDir }) => {
|
|
387
|
-
sealRelease({ configRoot: appRoot, managedCargoTarget, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence });
|
|
389
|
+
sealRelease({ configRoot: appRoot, managedCargoTarget, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence, managedRoot });
|
|
388
390
|
verifySealedRelease(sealedDir);
|
|
389
391
|
if (cacheMode === "shared") markCacheEntrySuccessful({ layout: sharedLayout });
|
|
390
392
|
checkpoint(stateRoot, "sealed");
|
|
@@ -424,7 +426,7 @@ function sqlCipherFeatures(text) {
|
|
|
424
426
|
.filter((value) => /sqlcipher|openssl/i.test(value));
|
|
425
427
|
}
|
|
426
428
|
|
|
427
|
-
function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence }) {
|
|
429
|
+
function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence, managedRoot }) {
|
|
428
430
|
// Every app's right-release.config.mjs names installer/updater artifacts
|
|
429
431
|
// relative to its resolved native target. On a broker-managed host that
|
|
430
432
|
// directory is never populated (the broker owns CARGO_TARGET_DIR and
|
|
@@ -442,7 +444,12 @@ function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir,
|
|
|
442
444
|
if (managedCargoTarget && effectiveTargetPrefix !== targetPrefix && normalized.startsWith(effectiveTargetPrefix)) {
|
|
443
445
|
return path.join(managedCargoTarget, normalized.slice(effectiveTargetPrefix.length));
|
|
444
446
|
}
|
|
445
|
-
|
|
447
|
+
const local = path.resolve(configRoot, file);
|
|
448
|
+
if (existsSync(local)) return local;
|
|
449
|
+
const managed = resolveArtifactDir({ app: config.app, version: config.version, managedRoot });
|
|
450
|
+
const managedCandidate = path.join(managed, path.basename(file));
|
|
451
|
+
if (existsSync(managedCandidate)) return managedCandidate;
|
|
452
|
+
return local;
|
|
446
453
|
};
|
|
447
454
|
const sources = new Map();
|
|
448
455
|
const addSource = (source, role) => {
|
|
@@ -460,6 +467,7 @@ function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir,
|
|
|
460
467
|
if (existsSync(temp)) rmSync(temp, { recursive: true, force: true });
|
|
461
468
|
mkdirSync(temp, { recursive: true });
|
|
462
469
|
const files = [];
|
|
470
|
+
const artifactInputs = [];
|
|
463
471
|
const sourceNames = new Map();
|
|
464
472
|
for (const [source, roles] of sources) {
|
|
465
473
|
let name = path.basename(source);
|
|
@@ -467,6 +475,7 @@ function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir,
|
|
|
467
475
|
sourceNames.set(source, name);
|
|
468
476
|
copyFileSync(source, path.join(temp, name));
|
|
469
477
|
files.push({ role: [...roles].sort().join("+"), name, sha256: hashFile(source), sizeBytes: statSync(source).size });
|
|
478
|
+
artifactInputs.push({ source, name });
|
|
470
479
|
}
|
|
471
480
|
const routes = { patch: [], update: [] };
|
|
472
481
|
for (const artifact of target.installer?.artifacts ?? []) {
|
|
@@ -497,6 +506,10 @@ function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir,
|
|
|
497
506
|
signing: signingIdentity ? { ...signingIdentity, receipts: (() => {
|
|
498
507
|
const present = signingIdentity.receiptInputs.filter((file) => existsSync(file));
|
|
499
508
|
if (present.length === 0) throw new Error(`no signing receipts found to seal; expected at least ${signingIdentity.receiptInputs[0]}`);
|
|
509
|
+
if (platform === "mac") {
|
|
510
|
+
if (present.length !== signingIdentity.receiptInputs.length) throw new Error("missing current-build macOS signing or notarization receipt");
|
|
511
|
+
validateNotarizationReceipt({ receiptPath: signingIdentity.receiptInputs[1], archive: resolveArtifactPath(target.notarize.file) });
|
|
512
|
+
}
|
|
500
513
|
return present.map((file) => ({ file, sha256: hashFile(file) }));
|
|
501
514
|
})() } : null,
|
|
502
515
|
files,
|
|
@@ -514,6 +527,14 @@ function sealRelease({ configRoot, managedCargoTarget, nativeLayout, sealedDir,
|
|
|
514
527
|
const file = path.join(sealedDir, item.name);
|
|
515
528
|
try { chmodSync(file, 0o444); } catch { /* best effort; hashes are authoritative */ }
|
|
516
529
|
}
|
|
530
|
+
// Keep candidate installer/updater bytes outside app repositories. Sealed
|
|
531
|
+
// copies remain immutable release authority; managed copies support local
|
|
532
|
+
// retention and recovery without reintroducing repo-root artifacts.
|
|
533
|
+
materializeArtifacts({ app: config.app, version: config.version, managedRoot, files: artifactInputs });
|
|
534
|
+
completeArtifactLease({ app: config.app, version: config.version, managedRoot });
|
|
535
|
+
const retention = retainArtifacts({ app: config.app, managedRoot });
|
|
536
|
+
if (retention.state === "unknown") console.error(`[right-release] artifact retention skipped: ${retention.reason}`);
|
|
537
|
+
else if (retention.removed.length) console.log(`[right-release] artifact retention removed ${retention.removed.join(", ")}`);
|
|
517
538
|
}
|
|
518
539
|
|
|
519
540
|
function collectToolVersions(packageManager) {
|
package/cli/right-release.mjs
CHANGED
|
@@ -18,6 +18,7 @@ const commands = new Map([
|
|
|
18
18
|
["hardeningscan", "hardeningscan.mjs"],
|
|
19
19
|
["sign-windows", "sign-windows.mjs"],
|
|
20
20
|
["sign-updater", "sign-updater.mjs"],
|
|
21
|
+
["signing-agent", "signing-agent-command.mjs"],
|
|
21
22
|
["create-mac-updater", "create-mac-updater.mjs"],
|
|
22
23
|
["mirror-root-artifact", "mirror-root-artifact.mjs"],
|
|
23
24
|
["github", "github-release.mjs"],
|
|
@@ -31,7 +32,7 @@ const args = process.argv.slice(2);
|
|
|
31
32
|
const first = args[0];
|
|
32
33
|
if (first === "dev") {
|
|
33
34
|
import("../development.mjs").then(({ main }) => main(args.slice(1))).catch((error) => {
|
|
34
|
-
console.error(`right-release dev: ${error.message}`);
|
|
35
|
+
console.error(`right-release dev: ${error.code ? `[${error.code}] ` : ""}${error.message}`);
|
|
35
36
|
process.exitCode = 1;
|
|
36
37
|
});
|
|
37
38
|
} else {
|
|
@@ -69,6 +70,8 @@ if (first === "--version" || first === "-v") {
|
|
|
69
70
|
);
|
|
70
71
|
} else if (first === "cache") {
|
|
71
72
|
run("cache-command.mjs", args.slice(1));
|
|
73
|
+
} else if (first === "apple") {
|
|
74
|
+
run("apple-profile-renew.mjs", args.slice(1));
|
|
72
75
|
} else if (first === "publish") {
|
|
73
76
|
const rest = args.slice(1);
|
|
74
77
|
if (rest[0] === "cargo") {
|
|
@@ -166,9 +169,12 @@ Commands:
|
|
|
166
169
|
(npm-rightkit-publish) and verify registry bytes
|
|
167
170
|
publish cargo --crate <name> [--dry-run] Test, inspect, scan, and publish one crates.io package
|
|
168
171
|
publish swift [--repo <git-url>] [--dry-run] Test, tag, resolve a fresh exact-pin consumer, and publish RightKitSwift to its git repo
|
|
169
|
-
model promote --authority
|
|
172
|
+
model promote --authority <app-key> --config <file> [--dry-run]
|
|
170
173
|
Sign and promote one runtime/model artifact
|
|
171
174
|
addon doctor|build|upload|adopt <options> Manage a signed headless add-on
|
|
175
|
+
apple profile renew [--config <file>] [--profile <file>] [--dry-run]
|
|
176
|
+
Renew macOS profile for current Keychain signer
|
|
177
|
+
signing-agent install|uninstall|status Manage invisible Aqua-session signing service
|
|
172
178
|
doctor [--platform mac|win] Inspect one app's release config
|
|
173
179
|
doctor --all Verify all Right Suite app release contracts
|
|
174
180
|
suite-doctor Verify all local Right Suite repositories
|
|
@@ -192,7 +198,7 @@ Commands:
|
|
|
192
198
|
lsclean <AppName.app> Clear macOS LaunchServices duplicates
|
|
193
199
|
generate-dmg-background <options> Generate the branded multi-resolution DMG background
|
|
194
200
|
mirror-root-artifact --file <path> --package-root <dir>
|
|
195
|
-
Persist
|
|
201
|
+
Persist artifact in RIGHT_RELEASE_ARTIFACT_DIR
|
|
196
202
|
github --release <id> --platform mac|win [--repo owner/repo] [--dry-run]
|
|
197
203
|
Attach sealed signed artifacts to a verified GitHub Release
|
|
198
204
|
|
package/create-mac-updater.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from "./background-process.mjs";
|
|
3
3
|
import { existsSync, rmSync } from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import { signMacUpdaterFiles } from "./macos-signing-session.mjs";
|
|
6
6
|
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
let app;
|
|
@@ -27,18 +27,6 @@ const env = { ...process.env };
|
|
|
27
27
|
// entries. Tauri's Rust extractor writes those entries as real bundle files,
|
|
28
28
|
// invalidating the Developer ID seal and causing syspolicyd to trash the app.
|
|
29
29
|
env.COPYFILE_DISABLE = "1";
|
|
30
|
-
if (!dryRun && !env.TAURI_SIGNING_PRIVATE_KEY) {
|
|
31
|
-
try {
|
|
32
|
-
env.TAURI_SIGNING_PRIVATE_KEY = execFileSync(
|
|
33
|
-
"security",
|
|
34
|
-
["find-generic-password", "-a", os.userInfo().username, "-s", "rightsuite-updater-key", "-w"],
|
|
35
|
-
{ encoding: "utf8" },
|
|
36
|
-
).trim();
|
|
37
|
-
} catch {
|
|
38
|
-
fail("shared updater key is missing from Keychain service rightsuite-updater-key");
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD ??= "";
|
|
42
30
|
|
|
43
31
|
rmSync(outputPath, { force: true });
|
|
44
32
|
rmSync(`${outputPath}.sig`, { force: true });
|
|
@@ -51,7 +39,12 @@ run("tar", [
|
|
|
51
39
|
path.dirname(appPath),
|
|
52
40
|
path.join(path.basename(appPath), "Contents"),
|
|
53
41
|
]);
|
|
54
|
-
run("pnpm", ["exec", "tauri", "signer", "sign", outputPath]);
|
|
42
|
+
if (dryRun) run("pnpm", ["exec", "tauri", "signer", "sign", outputPath]);
|
|
43
|
+
else {
|
|
44
|
+
console.log(`pnpm exec tauri signer sign ${outputPath}`);
|
|
45
|
+
try { signMacUpdaterFiles([outputPath], { cwd, env }); }
|
|
46
|
+
catch (error) { fail(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
|
|
47
|
+
}
|
|
55
48
|
|
|
56
49
|
function run(command, commandArgs) {
|
|
57
50
|
const envPrefix = command === "tar" ? "COPYFILE_DISABLE=1 " : "";
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash, X509Certificate } from "node:crypto";
|
|
2
|
+
import { closeSync, copyFileSync, existsSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "./background-process.mjs";
|
|
6
|
+
import { runMacCodesignCommands } from "./macos-signing-session.mjs";
|
|
7
|
+
|
|
8
|
+
export class MacosDevelopmentSigningError extends Error {
|
|
9
|
+
constructor(code, message, details = {}) {
|
|
10
|
+
super(message, details.cause ? { cause: details.cause } : undefined);
|
|
11
|
+
this.name = "MacosDevelopmentSigningError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
Object.assign(this, details);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_TEAM = "6KLGD3LLKF";
|
|
18
|
+
const output = (result) => `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
19
|
+
const captureOptions = (root, env) => ({ cwd: root, env, encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], windowsHide: true, shell: false });
|
|
20
|
+
|
|
21
|
+
function checked(command, args, context, code, identity) {
|
|
22
|
+
const result = context.run(command, args, captureOptions(context.root, context.env));
|
|
23
|
+
if (result.error || result.status !== 0) {
|
|
24
|
+
const diagnostic = result.error?.message || output(result) || `exit ${result.status ?? "unavailable"}${result.signal ? ` (${result.signal})` : ""}`;
|
|
25
|
+
const interaction = result.status === 36 || /errSecInteractionNotAllowed|User interaction is not allowed/i.test(diagnostic);
|
|
26
|
+
throw new MacosDevelopmentSigningError(interaction ? "DEV_MAC_SIGNING_KEY_UNAVAILABLE" : code,
|
|
27
|
+
`${identity ? `Signing identity ${JSON.stringify(identity)}: ` : ""}${command} failed (${result.status ?? "unavailable"}): ${diagnostic}. ${interaction
|
|
28
|
+
? "Run right-release dev from Terminal in a logged-in macOS GUI session where this identity's private key is accessible; SSH cannot authorize Keychain interaction."
|
|
29
|
+
: "Check the configured identity, profile and entitlements; confirm this identity and its private key are available in Keychain Access in a logged-in GUI session."}`,
|
|
30
|
+
{ identity, status: result.status, diagnostic, cause: result.error });
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readPlist(file, context) {
|
|
36
|
+
const result = checked("plutil", ["-convert", "json", "-o", "-", file], context, "DEV_MAC_SIGNING_CONFIG_INVALID");
|
|
37
|
+
try { return JSON.parse(result.stdout); }
|
|
38
|
+
catch { throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_CONFIG_INVALID", `Invalid plist JSON: ${file}`); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function decodeProfile(file, context) {
|
|
42
|
+
const cms = checked("security", ["cms", "-D", "-i", file], context, "DEV_MAC_SIGNING_PROFILE_INVALID");
|
|
43
|
+
// plutil JSON conversion rejects the date/data values in provisioning profiles.
|
|
44
|
+
const parser = "import base64,datetime,json,plistlib,sys\ndef encode(v):\n if isinstance(v,bytes): return base64.b64encode(v).decode('ascii')\n if isinstance(v,(datetime.date,datetime.datetime)): return v.isoformat()\n raise TypeError(type(v).__name__)\nprint(json.dumps(plistlib.loads(sys.stdin.buffer.read()),default=encode))";
|
|
45
|
+
const parsed = context.run("python3", ["-c", parser], { ...captureOptions(context.root, context.env), stdio: "pipe", input: cms.stdout });
|
|
46
|
+
try {
|
|
47
|
+
if (parsed.error || parsed.status !== 0) throw new Error(parsed.error?.message || output(parsed));
|
|
48
|
+
const profile = JSON.parse(parsed.stdout);
|
|
49
|
+
if (!Array.isArray(profile.TeamIdentifier) || profile.TeamIdentifier.length !== 1 || !profile.TeamIdentifier[0]) throw new Error("profile must name one team");
|
|
50
|
+
if (!profile.Platform?.includes("OSX")) throw new Error("profile must target OSX");
|
|
51
|
+
if (!(Date.parse(profile.ExpirationDate) > Date.now())) throw new Error("profile is expired or has no expiration date");
|
|
52
|
+
const certificates = (profile.DeveloperCertificates ?? []).map((data) => {
|
|
53
|
+
const cert = new X509Certificate(Buffer.from(data, "base64"));
|
|
54
|
+
return { name: /^CN=(.+)$/m.exec(cert.subject)?.[1], sha1: createHash("sha1").update(cert.raw).digest("hex").toUpperCase() };
|
|
55
|
+
});
|
|
56
|
+
if (!certificates.length) throw new Error("profile has no signing certificates");
|
|
57
|
+
return { profile, certificates, team: profile.TeamIdentifier[0] };
|
|
58
|
+
} catch (cause) {
|
|
59
|
+
throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_PROFILE_INVALID", `Cannot use provisioning profile ${file}: ${cause.message}. Configure a valid macOS profile authorizing this app and signing certificate.`, { cause });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveMacosDevelopmentSigning({ root, bundle, descriptor = {}, config = {}, env = process.env, run = spawnSync }) {
|
|
64
|
+
const context = { root, env, run };
|
|
65
|
+
const tauriRoot = path.dirname(path.resolve(root, descriptor.manifestPath ?? "src-tauri/Cargo.toml"));
|
|
66
|
+
let mac = {};
|
|
67
|
+
for (const name of ["tauri.conf.json", "tauri.macos.conf.json"]) {
|
|
68
|
+
const file = path.join(tauriRoot, name);
|
|
69
|
+
if (existsSync(file)) mac = { ...mac, ...JSON.parse(readFileSync(file, "utf8")).bundle?.macOS };
|
|
70
|
+
}
|
|
71
|
+
const sign = config.targets?.mac?.sign ?? {};
|
|
72
|
+
const entitlements = sign.entitlements ? path.resolve(root, sign.entitlements) : mac.entitlements ? path.resolve(tauriRoot, mac.entitlements) : null;
|
|
73
|
+
if (entitlements && !existsSync(entitlements)) throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_CONFIG_INVALID", `Configured entitlements file is missing: ${entitlements}`);
|
|
74
|
+
const tauriProfile = mac.provisioningProfile ?? mac.files?.["embedded.provisionprofile"] ?? mac.files?.["/embedded.provisionprofile"];
|
|
75
|
+
const configuredProfile = sign.provisioningProfile ? path.resolve(root, sign.provisioningProfile) : tauriProfile ? path.resolve(tauriRoot, tauriProfile) : null;
|
|
76
|
+
const embedded = path.join(bundle, "Contents", "embedded.provisionprofile");
|
|
77
|
+
// App preparation may already embed an app-specific profile (e.g. HeardRight).
|
|
78
|
+
let provisioningProfile = configuredProfile ?? (existsSync(embedded) ? embedded : null);
|
|
79
|
+
if (provisioningProfile && !existsSync(provisioningProfile)) throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_PROFILE_INVALID", `Configured provisioning profile is missing: ${provisioningProfile}`);
|
|
80
|
+
let profileInfo = provisioningProfile ? decodeProfile(provisioningProfile, context) : null;
|
|
81
|
+
const override = env.APPLE_SIGNING_IDENTITY?.trim() || env.APPLE_DEVELOPER_ID?.trim();
|
|
82
|
+
const releaseTeam = env.APPLE_TEAM_ID?.trim() || DEFAULT_TEAM;
|
|
83
|
+
const configuredIdentity = sign.identity?.trim() || mac.signingIdentity?.trim();
|
|
84
|
+
if (override === "-" || (!profileInfo && configuredIdentity === "-")) throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_IDENTITY_INVALID", 'Signing identity "-" is ad hoc; configure APPLE_SIGNING_IDENTITY or APPLE_DEVELOPER_ID with a real Team identity.', { identity: "-" });
|
|
85
|
+
const requestedIdentity = override || (!profileInfo ? configuredIdentity : undefined);
|
|
86
|
+
const expected = requestedIdentity || profileInfo?.certificates.map((cert) => cert.name).join(" or ") || `Developer ID Application (team ${releaseTeam})`;
|
|
87
|
+
const found = checked("security", ["find-identity", "-v", "-p", "codesigning"], context, "DEV_MAC_SIGNING_IDENTITY_MISSING", expected);
|
|
88
|
+
const identities = [...String(found.stdout ?? "").matchAll(/([A-Fa-f0-9]{40})\s+"([^"\n]+)"/g)].map((match) => ({ sha1: match[1].toUpperCase(), name: match[2] }));
|
|
89
|
+
const matchesRequest = (item, requested) => item.name === requested || item.sha1 === requested?.toUpperCase();
|
|
90
|
+
const profiled = (info) => identities.filter((item) => info.certificates.some((cert) => cert.sha1 === item.sha1) && /^(Apple Development|Mac Developer|Developer ID Application):/.test(item.name));
|
|
91
|
+
// A configured profile can go stale when its certificate is renewed: prefer a sibling profile for the
|
|
92
|
+
// same app identifier that authorizes a certificate whose private key is in this Keychain.
|
|
93
|
+
if (profileInfo && !profiled(profileInfo).length && configuredProfile) {
|
|
94
|
+
const appId = (info) => info.profile.Entitlements?.["com.apple.application-identifier"] ?? info.profile.Entitlements?.["application-identifier"];
|
|
95
|
+
const siblings = readdirSync(path.dirname(configuredProfile)).filter((name) => name.endsWith(".provisionprofile")).map((name) => path.join(path.dirname(configuredProfile), name)).filter((file) => path.resolve(file) !== path.resolve(configuredProfile));
|
|
96
|
+
const usable = siblings.flatMap((file) => { try { const info = decodeProfile(file, context); return appId(info) === appId(profileInfo) && profiled(info).length ? [{ file, info }] : []; } catch { return []; } })
|
|
97
|
+
.sort((a, b) => Date.parse(b.info.profile.ExpirationDate) - Date.parse(a.info.profile.ExpirationDate));
|
|
98
|
+
if (usable.length) { provisioningProfile = usable[0].file; profileInfo = usable[0].info; }
|
|
99
|
+
}
|
|
100
|
+
let candidates = profileInfo
|
|
101
|
+
? profiled(profileInfo)
|
|
102
|
+
: identities.filter((item) => item.name.startsWith("Developer ID Application:") && item.name.endsWith(`(${releaseTeam})`));
|
|
103
|
+
if (requestedIdentity) candidates = candidates.filter((item) => matchesRequest(item, requestedIdentity));
|
|
104
|
+
else if (profileInfo && configuredIdentity && candidates.some((item) => matchesRequest(item, configuredIdentity))) candidates = candidates.filter((item) => matchesRequest(item, configuredIdentity));
|
|
105
|
+
if (candidates.length !== 1) throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_IDENTITY_MISSING", `Signing identity ${JSON.stringify(expected)}: ${candidates.length ? "multiple matching identities" : "no valid matching identity/private key"} in Keychain${provisioningProfile ? ` for ${provisioningProfile}` : ""}. ${candidates.length ? "Set APPLE_SIGNING_IDENTITY or APPLE_DEVELOPER_ID to one matching identity." : "Confirm the profile-authorized certificate and its private key are available in Keychain Access in a logged-in GUI session."}`, { identity: expected });
|
|
106
|
+
return { identity: candidates[0].name, certificateSha1: candidates[0].sha1, teamIdentifier: profileInfo?.team ?? releaseTeam, entitlements, provisioningProfile };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function bundleInfo(directory, context) {
|
|
110
|
+
for (const location of ["Contents/Info.plist", "Resources/Info.plist", "Info.plist"]) {
|
|
111
|
+
const file = path.join(directory, location);
|
|
112
|
+
if (existsSync(file)) return readPlist(file, context);
|
|
113
|
+
}
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function macho(file) {
|
|
118
|
+
const fd = openSync(file, "r");
|
|
119
|
+
try {
|
|
120
|
+
const magic = Buffer.alloc(4);
|
|
121
|
+
return readSync(fd, magic, 0, 4, 0) === 4 && new Set(["feedface", "cefaedfe", "feedfacf", "cffaedfe", "cafebabe", "bebafeca", "cafebabf", "bfbafeca"]).has(magic.toString("hex"));
|
|
122
|
+
} finally { closeSync(fd); }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function signMacosDevelopmentBundle(options) {
|
|
126
|
+
const { root, bundle, env = process.env, run = spawnSync } = options;
|
|
127
|
+
const context = { root, env, run };
|
|
128
|
+
const signing = resolveMacosDevelopmentSigning({ ...options, env, run });
|
|
129
|
+
const { identity, certificateSha1, teamIdentifier, entitlements, provisioningProfile } = signing;
|
|
130
|
+
const embedded = path.join(bundle, "Contents", "embedded.provisionprofile");
|
|
131
|
+
if (provisioningProfile && path.resolve(provisioningProfile) !== path.resolve(embedded)) copyFileSync(provisioningProfile, embedded);
|
|
132
|
+
const scratch = mkdtempSync(path.join(os.tmpdir(), "rightkit-dev-signing-"));
|
|
133
|
+
try {
|
|
134
|
+
const appInfo = bundleInfo(bundle, context);
|
|
135
|
+
const appEntitlements = entitlements ? readPlist(entitlements, context) : {};
|
|
136
|
+
// Loose tools cannot claim the parent app's profile-restricted entitlements.
|
|
137
|
+
// Retain their existing entitlements; give unsigned tools only the app's runtime relaxations.
|
|
138
|
+
const runtimeEntitlements = Object.fromEntries(Object.entries(appEntitlements).filter(([key]) => key.startsWith("com.apple.security.cs.") || key === "com.apple.security.device.audio-input"));
|
|
139
|
+
const runtimeFile = path.join(scratch, "runtime.plist");
|
|
140
|
+
writeFileSync(runtimeFile, JSON.stringify(runtimeEntitlements));
|
|
141
|
+
checked("plutil", ["-convert", "xml1", runtimeFile], context, "DEV_MAC_SIGNING_CONFIG_INVALID", identity);
|
|
142
|
+
// Apps keep sidecar/helper variants beside their main entitlements file:
|
|
143
|
+
// entitlements-engine.plist, entitlements-cloudsync.plist, entitlements-credentials.plist.
|
|
144
|
+
const entitlementVariants = entitlements ? readdirSync(path.dirname(entitlements))
|
|
145
|
+
.filter((name) => name.startsWith(`${path.basename(entitlements, path.extname(entitlements))}-`) && name.endsWith(".plist"))
|
|
146
|
+
.map((name) => ({ file: path.join(path.dirname(entitlements), name), suffix: path.basename(name, ".plist").slice(path.basename(entitlements, path.extname(entitlements)).length + 1).toLowerCase() }))
|
|
147
|
+
.sort((a, b) => b.suffix.length - a.suffix.length) : [];
|
|
148
|
+
const targets = [];
|
|
149
|
+
const bundleExecutables = new Set();
|
|
150
|
+
const visit = (directory, isBundle = false) => {
|
|
151
|
+
const info = isBundle ? bundleInfo(directory, context) : {};
|
|
152
|
+
if (info.CFBundleExecutable) {
|
|
153
|
+
for (const file of [path.join(directory, "Contents/MacOS", info.CFBundleExecutable), path.join(directory, info.CFBundleExecutable)]) {
|
|
154
|
+
if (existsSync(file)) bundleExecutables.add(realpathSync(file));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
158
|
+
const file = path.join(directory, entry.name);
|
|
159
|
+
if (entry.isSymbolicLink()) continue;
|
|
160
|
+
if (entry.isDirectory()) visit(file, /\.(app|framework|xpc|appex|bundle)$/.test(entry.name));
|
|
161
|
+
else if (entry.isFile() && macho(file)) targets.push({ file, info: null });
|
|
162
|
+
}
|
|
163
|
+
if (isBundle) targets.push({ file: directory, info });
|
|
164
|
+
};
|
|
165
|
+
visit(bundle, true);
|
|
166
|
+
const ordered = targets.filter((target) => target.info || !bundleExecutables.has(realpathSync(target.file)))
|
|
167
|
+
.sort((a, b) => b.file.split(path.sep).length - a.file.split(path.sep).length || a.file.localeCompare(b.file));
|
|
168
|
+
const commands = [];
|
|
169
|
+
for (const target of ordered) {
|
|
170
|
+
// Bind the exact profile-authorized certificate even when names collide in Keychain.
|
|
171
|
+
const args = ["--force", "--options", "runtime", "--sign", certificateSha1];
|
|
172
|
+
const parent = target.file === bundle;
|
|
173
|
+
const profiledHelper = target.info && existsSync(path.join(target.file, "Contents/embedded.provisionprofile")) && target.info.CFBundleIdentifier === appInfo.CFBundleIdentifier;
|
|
174
|
+
if (profiledHelper && !parent && provisioningProfile) copyFileSync(embedded, path.join(target.file, "Contents/embedded.provisionprofile"));
|
|
175
|
+
const variant = !parent && entitlementVariants.find((item) => path.basename(target.file).replace(/\.(app|xpc|appex|bundle)$/, "").toLowerCase().endsWith(item.suffix));
|
|
176
|
+
if (variant) args.push("--entitlements", variant.file);
|
|
177
|
+
else if (entitlements && (parent || profiledHelper)) args.push("--entitlements", entitlements);
|
|
178
|
+
else {
|
|
179
|
+
const existing = run("codesign", ["--display", "--entitlements", ":-", target.file], captureOptions(root, env));
|
|
180
|
+
if (existing.status === 0 && /<plist|<dict/.test(String(existing.stdout ?? ""))) args.push("--preserve-metadata=entitlements");
|
|
181
|
+
else if (!target.info && (statSync(target.file).mode & 0o111) && !/\.(dylib|so)$/.test(target.file) && Object.keys(runtimeEntitlements).length) args.push("--entitlements", runtimeFile);
|
|
182
|
+
}
|
|
183
|
+
commands.push([...args, target.file]);
|
|
184
|
+
}
|
|
185
|
+
const signed = runMacCodesignCommands(commands, { ...options.sessionOptions, cwd: root, env, run });
|
|
186
|
+
for (const [index, args] of commands.entries()) checked("codesign", args, { ...context, run: () => signed[index] }, "DEV_MAC_SIGNING_FAILED", identity);
|
|
187
|
+
checked("codesign", ["--verify", "--strict", "--deep", bundle], context, "DEV_MAC_SIGNATURE_INVALID", identity);
|
|
188
|
+
const details = output(checked("codesign", ["--display", "--verbose=4", bundle], context, "DEV_MAC_SIGNATURE_INVALID", identity));
|
|
189
|
+
const actualTeam = /^TeamIdentifier=(.+)$/m.exec(details)?.[1]?.trim();
|
|
190
|
+
if (actualTeam !== teamIdentifier || /^Signature=adhoc$/m.test(details)) throw new MacosDevelopmentSigningError("DEV_MAC_SIGNING_TEAM_MISMATCH", `Signing identity ${JSON.stringify(identity)}: expected TeamIdentifier=${teamIdentifier}, found ${actualTeam ?? "missing"}. Refusing dev installation.`, { identity, expectedTeam: teamIdentifier, actualTeam });
|
|
191
|
+
return { ...signing, provisioningProfile: provisioningProfile ? embedded : null, verified: "strict-deep", signedTargets: ordered.length };
|
|
192
|
+
} finally { rmSync(scratch, { recursive: true, force: true }); }
|
|
193
|
+
}
|
package/development.mjs
CHANGED
|
@@ -8,8 +8,10 @@ import { assertReleaseExecutionAllowed } from "./execution-policy.mjs";
|
|
|
8
8
|
import { acquireManagedRunLease, managedRuntimeClient, runCargoSync } from "./managed-cargo.mjs";
|
|
9
9
|
import { BUNDLE_MARKERS, bundleMarkerBuffer, readTauriBundleMarkers } from "./tauri-bundle-marker.mjs";
|
|
10
10
|
import { installPortableDevelopment } from "./portable-development.mjs";
|
|
11
|
+
import { signMacosDevelopmentBundle } from "./development-signing.mjs";
|
|
11
12
|
|
|
12
13
|
export const DEVELOPMENT_PROFILE = "release-iterate";
|
|
14
|
+
const RELEASE_ONLY_APPLE_ENV = ["APPLE_ID", "APPLE_PASSWORD", "APPLE_API_KEY", "APPLE_API_ISSUER", "APPLE_API_KEY_PATH", "APPLE_CERTIFICATE", "APPLE_CERTIFICATE_PASSWORD"];
|
|
13
15
|
|
|
14
16
|
export function developmentEnvironment(env = process.env) {
|
|
15
17
|
const result = {
|
|
@@ -18,12 +20,9 @@ export function developmentEnvironment(env = process.env) {
|
|
|
18
20
|
RIGHTKIT_CARGO_PROFILE: DEVELOPMENT_PROFILE,
|
|
19
21
|
TAURI_SIGNING_PRIVATE_KEY: "",
|
|
20
22
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: "",
|
|
21
|
-
APPLE_SIGNING_IDENTITY: "-",
|
|
22
|
-
APPLE_ID: "",
|
|
23
|
-
APPLE_PASSWORD: "",
|
|
24
|
-
APPLE_TEAM_ID: "",
|
|
25
23
|
CSC_IDENTITY_AUTO_DISCOVERY: "false",
|
|
26
24
|
};
|
|
25
|
+
for (const key of RELEASE_ONLY_APPLE_ENV) delete result[key];
|
|
27
26
|
delete result.CARGO_INCREMENTAL;
|
|
28
27
|
const client = managedRuntimeClient(result);
|
|
29
28
|
if (client) {
|
|
@@ -76,8 +75,8 @@ export function runDevelopment({ root, platform, config, buildOnly = false, dryR
|
|
|
76
75
|
const startedMs = Date.now();
|
|
77
76
|
for (const command of target.build) execute(command, { root, env: childEnv, dryRun, run, label: "build" });
|
|
78
77
|
if (buildOnly || !target.install) return { built: true, installed: false };
|
|
79
|
-
install(target.install, { root, platform, env: childEnv, run, sinceMs: startedMs });
|
|
80
|
-
return { built: true, installed: true };
|
|
78
|
+
const signing = install(target.install, { root, platform, config, env: childEnv, run, sinceMs: startedMs });
|
|
79
|
+
return { built: true, installed: true, ...(platform === "mac" && target.install.kind === "tauri" ? { signing } : {}) };
|
|
81
80
|
} finally { lease.release(); }
|
|
82
81
|
}
|
|
83
82
|
|
|
@@ -85,7 +84,7 @@ export function assertNativeDevelopmentHost(platform, host = process.platform) {
|
|
|
85
84
|
if ((platform === "win" && host !== "win32") || (platform === "mac" && host !== "darwin")) throw new Error(`development target ${platform} requires its native host`);
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
function install(descriptor, { root, platform, env, run, sinceMs = null }) {
|
|
87
|
+
function install(descriptor, { root, platform, config, env, run, sinceMs = null }) {
|
|
89
88
|
if (descriptor.kind === "command") return execute(descriptor, { root, env, run, label: "install" });
|
|
90
89
|
if (descriptor.kind === "portable") return installPortableDevelopment(descriptor, { root, platform: platform === "win" ? "win32" : "darwin", env });
|
|
91
90
|
if (platform === "win") {
|
|
@@ -111,11 +110,10 @@ function install(descriptor, { root, platform, env, run, sinceMs = null }) {
|
|
|
111
110
|
const backup = `${destination}.rightkit-backup-${process.pid}`;
|
|
112
111
|
rmSync(staging, { recursive: true, force: true });
|
|
113
112
|
rmSync(backup, { recursive: true, force: true });
|
|
114
|
-
cpSync(app, staging, { recursive: true });
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (verified.status !== 0) { rmSync(staging, { recursive: true, force: true }); throw new Error(`ad-hoc signature verification failed (${verified.status}): ${staging}`); }
|
|
113
|
+
cpSync(app, staging, { recursive: true, verbatimSymlinks: true });
|
|
114
|
+
let signing;
|
|
115
|
+
try { signing = signMacosDevelopmentBundle({ root, bundle: staging, descriptor, config, env, run }); }
|
|
116
|
+
catch (error) { rmSync(staging, { recursive: true, force: true }); throw error; }
|
|
119
117
|
let backedUp = false;
|
|
120
118
|
try {
|
|
121
119
|
if (existsSync(destination)) { renameSync(destination, backup); backedUp = true; }
|
|
@@ -128,6 +126,9 @@ function install(descriptor, { root, platform, env, run, sinceMs = null }) {
|
|
|
128
126
|
rmSync(staging, { recursive: true, force: true });
|
|
129
127
|
throw error;
|
|
130
128
|
}
|
|
129
|
+
if (signing.provisioningProfile) signing.provisioningProfile = path.join(destination, "Contents", "embedded.provisionprofile");
|
|
130
|
+
console.log(`right-release dev: installed ${destination}; identity=${signing.identity}; TeamIdentifier=${signing.teamIdentifier}; profile=${signing.provisioningProfile ?? "none"}`);
|
|
131
|
+
return signing;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
export function resolveTauriArtifact(descriptor, { root, platform, env = process.env, runCargo = runCargoSync }) {
|
|
@@ -268,6 +269,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
268
269
|
const absoluteConfig = path.resolve(configPath);
|
|
269
270
|
const safeEnv = developmentEnvironment(process.env);
|
|
270
271
|
for (const [key, value] of Object.entries(safeEnv)) process.env[key] = value;
|
|
272
|
+
for (const key of RELEASE_ONLY_APPLE_ENV) delete process.env[key];
|
|
271
273
|
delete process.env.CARGO_INCREMENTAL;
|
|
272
274
|
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path");
|
|
273
275
|
if (pathKey) process.env[pathKey] = safeEnv[pathKey] ?? process.env[pathKey];
|
|
@@ -279,5 +281,5 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
279
281
|
}
|
|
280
282
|
|
|
281
283
|
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
282
|
-
main().catch((error) => { console.error(`right-release dev: ${error.message}`); process.exitCode = 1; });
|
|
284
|
+
main().catch((error) => { console.error(`right-release dev: ${error.code ? `[${error.code}] ` : ""}${error.message}`); process.exitCode = 1; });
|
|
283
285
|
}
|