@rightkit/release 0.2.174 → 0.4.0
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/README.md +200 -2
- package/addon-command.mjs +3 -4
- package/android-play-upload.mjs +2 -1
- package/apple-profile-renew.mjs +11 -3
- package/artifact-storage.mjs +35 -2
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/darwin/rightkit-swap.sign.json +1 -1
- package/build-release.mjs +138 -47
- package/cli/right-release.mjs +40 -5
- package/codesign-environment.mjs +2 -2
- package/command-spec.mjs +80 -0
- package/content-digest.mjs +209 -0
- package/deps-cargo.mjs +55 -0
- package/deps.mjs +7 -0
- package/development.mjs +17 -6
- package/direct-bootstrap.mjs +5 -6
- package/disk-command.mjs +1 -0
- package/disk-policy.json +6 -2
- package/disk-stable-root.mjs +13 -9
- package/dist-scan.mjs +102 -0
- package/dmg-build.mjs +53 -4
- package/github-updater-release.mjs +70 -7
- package/icon-pack.mjs +117 -0
- package/in-place-install.mjs +17 -14
- package/init-command.mjs +116 -0
- package/install-app-windows.mjs +24 -7
- package/install-app.mjs +56 -42
- package/ios-asc-credentials.mjs +163 -0
- package/ios-build-context.mjs +129 -0
- package/ios-build-upload.mjs +282 -0
- package/ios-credentials-command.mjs +70 -0
- package/ios-dev.mjs +248 -0
- package/ios-host.mjs +76 -0
- package/ios-lock.mjs +75 -0
- package/ios-package-recipe.mjs +707 -0
- package/ios-testflight.mjs +266 -0
- package/ios-upload.mjs +313 -0
- package/local-publish.mjs +390 -47
- package/macho-approval-hash.mjs +181 -0
- package/managed-cargo.mjs +57 -1
- package/media-sidecar.mjs +20 -4
- package/mirror-root-artifact.mjs +74 -22
- package/native-cargo-layout.mjs +47 -4
- package/npm-license-notices.mjs +159 -0
- package/ownership-command.mjs +2 -1
- package/ownership-dependency-allowlist.json +2 -1
- package/ownership-evidence.mjs +30 -18
- package/ownership-key-command.mjs +69 -2
- package/ownership-mandatory-set.mjs +66 -16
- package/ownership-repositories.json +1 -1
- package/ownership-scope.mjs +94 -0
- package/ownership.mjs +86 -11
- package/package-recipe-targets.mjs +56 -2
- package/package.json +1 -1
- package/pe-content.mjs +133 -0
- package/preflight.mjs +14 -7
- package/project-type.mjs +3 -1
- package/protected-credentials.mjs +28 -1
- package/publication-artifacts.mjs +30 -5
- package/publication-gate.mjs +83 -5
- package/publish-cargo.mjs +124 -18
- package/publish-update.mjs +15 -2
- package/qa-build-features.mjs +16 -13
- package/qa-contract.mjs +30 -2
- package/qa-control-scan.mjs +4 -0
- package/release-dependencies-command.mjs +45 -0
- package/release-dependencies.mjs +60 -0
- package/release-invocation.mjs +118 -8
- package/release-switch-registry.json +126 -27
- package/release-switch-scan.mjs +62 -24
- package/release.mjs +144 -21
- package/rightkit-ownership.json +46 -2
- package/rightkit-versions.json +380 -189
- package/runtime-budgets.mjs +134 -23
- package/sign-updater.mjs +56 -59
- package/source-gate.mjs +8 -1
- package/stage-sidecar.mjs +634 -69
- package/target-distribution.mjs +5 -0
- package/test-command.mjs +190 -0
- package/toolchain-swift.mjs +239 -0
- package/upload-release.mjs +7 -2
- package/web-build.mjs +67 -0
package/build-release.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
closeSync,
|
|
6
6
|
copyFileSync,
|
|
7
7
|
existsSync,
|
|
8
|
-
mkdtempSync,
|
|
9
8
|
mkdirSync,
|
|
10
9
|
openSync,
|
|
11
10
|
readFileSync,
|
|
@@ -18,8 +17,8 @@ import {
|
|
|
18
17
|
unlinkSync,
|
|
19
18
|
writeFileSync,
|
|
20
19
|
} from "node:fs";
|
|
21
|
-
import { tmpdir } from "node:os";
|
|
22
20
|
import path from "node:path";
|
|
21
|
+
import { iosOwnershipScope } from "./ownership-scope.mjs";
|
|
23
22
|
import { spawn, spawnSync } from "./background-process.mjs";
|
|
24
23
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
25
24
|
import { assertPrimaryReleaseCheckout, dirtyBuildInputs, resolveReleaseBuildInputs } from "./release-invocation.mjs";
|
|
@@ -37,6 +36,7 @@ import { ensureLegacyTargetLink, resolveReleaseCacheMode } from "./release-cache
|
|
|
37
36
|
import { resolveTargetRoot } from "./cargo-target.mjs";
|
|
38
37
|
import { assertNsisInPlaceUpgradeContract } from "./nsis-upgrade-contract.mjs";
|
|
39
38
|
import { collectSwiftToolVersions, planSwiftBuild } from "./swift-build-context.mjs";
|
|
39
|
+
import { planIosNativeBuild, resolveIosBuildContext } from "./ios-build-context.mjs";
|
|
40
40
|
import { packageCommandOptional, requiredBuildExecutables, resolveProjectType, targetUsesMsix } from "./project-type.mjs";
|
|
41
41
|
import { buildPathPrefix, collectPreflight, formatPreflight, preflightFailures } from "./preflight.mjs";
|
|
42
42
|
import { assertCleanSource } from "./source-gate.mjs";
|
|
@@ -46,6 +46,15 @@ import { cancelAndReapManagedRequest, firstManagedRequestId, releaseProgressLimi
|
|
|
46
46
|
import { beginArtifactLease, completeArtifactLease, materializeArtifacts, retainArtifacts, resolveArtifactDir, resolveManagedRoot } from "./artifact-storage.mjs";
|
|
47
47
|
import { macosCodesignEnvironment } from "./codesign-environment.mjs";
|
|
48
48
|
import { ANDROID_RECEIPT_NAME, androidSealEntry, validateAndroidTarget, verifyAndroidReceipt } from "./android-release.mjs";
|
|
49
|
+
import { IOS_RECEIPT_NAME, iosSealEntry, verifyIosReceipt } from "./ios-package-recipe.mjs";
|
|
50
|
+
import { expandIosPackageTarget } from "./package-recipe-targets.mjs";
|
|
51
|
+
import { assertUpdaterSignature } from "./github-updater-release.mjs";
|
|
52
|
+
|
|
53
|
+
// The iOS lane's own packaging code, kept out of the win/mac fingerprint for the same reason as the Android one.
|
|
54
|
+
const IOS_PIPELINE_FINGERPRINT = createHash("sha256")
|
|
55
|
+
.update(["ios-package-recipe.mjs", "ios-host.mjs"].map((file) => `${file}:${createHash("sha256").update(readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), file))).digest("hex")}`).join("\n"))
|
|
56
|
+
.digest("hex")
|
|
57
|
+
.slice(0, 16);
|
|
49
58
|
|
|
50
59
|
// Fingerprint of the pipeline code that can change the bytes we ship or the way
|
|
51
60
|
// they are signed — deliberately NOT the package version, which moves for docs
|
|
@@ -66,15 +75,18 @@ const ANDROID_PIPELINE_FINGERPRINT = createHash("sha256")
|
|
|
66
75
|
.slice(0, 16);
|
|
67
76
|
|
|
68
77
|
/** Assemble preflight inputs from the app's own files (mirrors release.mjs). */
|
|
69
|
-
function buildPreflight({ config, configPath, appRoot, repoRoot, platform, nativeLayout, target }) {
|
|
78
|
+
function buildPreflight({ config, configPath, appRoot, repoRoot, platform, nativeLayout, target, pinnedCargoLocks = [] }) {
|
|
70
79
|
let version;
|
|
71
80
|
try {
|
|
72
81
|
version = JSON.parse(readFileSync(path.join(appRoot, "package.json"), "utf8")).version;
|
|
73
82
|
} catch {
|
|
74
83
|
version = undefined;
|
|
75
84
|
}
|
|
85
|
+
// An iOS target is built from its Xcode project and local packages only; the ownership scan covers just those roots.
|
|
86
|
+
const ownershipScope = platform === "ios" ? iosOwnershipScope({ repoRoot, appRoot, target, nativeAssembly: config?.nativeAssembly }) : null;
|
|
76
87
|
return collectPreflight({
|
|
77
88
|
platform,
|
|
89
|
+
ownershipScope,
|
|
78
90
|
appRoot,
|
|
79
91
|
repoRoot,
|
|
80
92
|
app: config.app,
|
|
@@ -83,8 +95,11 @@ function buildPreflight({ config, configPath, appRoot, repoRoot, platform, nativ
|
|
|
83
95
|
// collision is already enforced downstream, and duplicating it here would
|
|
84
96
|
// reject a legitimate resume of an in-flight build.
|
|
85
97
|
version: undefined,
|
|
86
|
-
cargoLockPaths: nativeLayout?.lockCandidates ?? [],
|
|
98
|
+
cargoLockPaths: [...(nativeLayout?.lockCandidates ?? []), ...pinnedCargoLocks],
|
|
87
99
|
projectType: resolveProjectType({ appRoot, config, target }),
|
|
100
|
+
// A native iOS app has no broker target or bridge; it still gets the Rust budget and lock checks when it pins a Cargo.lock.
|
|
101
|
+
rust: Boolean(nativeLayout) || pinnedCargoLocks.length > 0,
|
|
102
|
+
bridge: Boolean(nativeLayout),
|
|
88
103
|
msix: targetUsesMsix(target),
|
|
89
104
|
});
|
|
90
105
|
}
|
|
@@ -109,7 +124,7 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
109
124
|
else if (arg === "-h" || arg === "--help") usage(0);
|
|
110
125
|
else fail(`unknown argument: ${arg}`);
|
|
111
126
|
}
|
|
112
|
-
if (platform !== "win" && platform !== "mac" && platform !== "android") fail("--platform must be win, mac or
|
|
127
|
+
if (platform !== "win" && platform !== "mac" && platform !== "android" && platform !== "ios") fail("--platform must be win, mac, android or ios");
|
|
113
128
|
if (resumeFrom && !["prepackage", "sign", "package", "post-sign", "harden"].includes(resumeFrom)) fail("--resume-from must be prepackage, sign, package, post-sign, or harden");
|
|
114
129
|
|
|
115
130
|
const configPath = path.resolve(configName);
|
|
@@ -141,8 +156,8 @@ if (dryRun) {
|
|
|
141
156
|
const config = (await import(`${pathToFileURL(configPath).href}?dry-run=${commit}`)).default;
|
|
142
157
|
if (!config?.app || !config?.version) fail("release config must expose app and version");
|
|
143
158
|
const target = config.targets?.[platform];
|
|
144
|
-
// Android builds with target.gradle (validated by release.mjs), not a package command.
|
|
145
|
-
if (platform !== "android" && !target?.package && !packageCommandOptional(target, resolveProjectType({ appRoot, config, target }))) fail(`${config.app} has no ${platform} package command`);
|
|
159
|
+
// Android builds with target.gradle (validated by release.mjs), not a package command; iOS with targets.ios.iosPackage.
|
|
160
|
+
if (platform !== "android" && platform !== "ios" && !target?.package && !packageCommandOptional(target, resolveProjectType({ appRoot, config, target }))) fail(`${config.app} has no ${platform} package command`);
|
|
146
161
|
console.log(`right-release build dry-run: ${config.app} ${config.version} ${platform}`);
|
|
147
162
|
console.log(`config: ${configPath}`);
|
|
148
163
|
console.log(`commit: ${commit}`);
|
|
@@ -166,7 +181,7 @@ try {
|
|
|
166
181
|
// turn an already-built, signed installer into a needless full rebuild.
|
|
167
182
|
if (!resumeFrom) {
|
|
168
183
|
assertCleanSource({
|
|
169
|
-
status: git(repoRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]),
|
|
184
|
+
status: git(repoRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], { trim: false }), // untrimmed: a leading space is the first record's X column (#782)
|
|
170
185
|
commandId: "right-release build",
|
|
171
186
|
});
|
|
172
187
|
}
|
|
@@ -181,24 +196,41 @@ try {
|
|
|
181
196
|
fail("app and version must be safe release-id components");
|
|
182
197
|
}
|
|
183
198
|
const target = config.targets?.[platform];
|
|
184
|
-
if (platform !== "android" && !target?.package && !packageCommandOptional(target, resolveProjectType({ appRoot, config, target }))) fail(`${config.app} has no ${platform} package command`);
|
|
199
|
+
if (platform !== "android" && platform !== "ios" && !target?.package && !packageCommandOptional(target, resolveProjectType({ appRoot, config, target }))) fail(`${config.app} has no ${platform} package command`);
|
|
200
|
+
// iOS: the same strict validation release.mjs applies; yields the IPA path and the team and bundle id the seal pins.
|
|
201
|
+
let iosSpec = null;
|
|
202
|
+
if (platform === "ios") {
|
|
203
|
+
try { iosSpec = expandIosPackageTarget(target, { appRoot }).recipe?.spec ?? null; } catch (error) { fail(`${config.app} ios ${error.message}`); }
|
|
204
|
+
if (!iosSpec) fail(`${config.app} ios must declare targets.ios.iosPackage`);
|
|
205
|
+
}
|
|
185
206
|
// Same validation release.mjs applies; yields the normalised upload-certificate pin and the AAB path.
|
|
186
207
|
let androidTarget = null;
|
|
187
208
|
if (platform === "android") {
|
|
188
209
|
try { androidTarget = validateAndroidTarget(target, { root: appRoot }); } catch (error) { fail(`${config.app} android ${error.message}`); }
|
|
189
210
|
}
|
|
190
211
|
const isSwiftBuild = resolveProjectType({ appRoot, config, target }) === "swiftpm";
|
|
191
|
-
|
|
212
|
+
// A native iOS app (xcodegen project; no Rust library unless nativeAssembly.cargoManifest declares one) has no src-tauri and no
|
|
213
|
+
// declared nativeAssembly manifest: it owns no Cargo layout (null), and every Cargo-derived value below is skipped for it.
|
|
214
|
+
// The Xcode tools are asserted present, then probed, inside resolveIosBuildContext (a missing tool fails with the normal message).
|
|
215
|
+
const iosContext = iosSpec !== null
|
|
216
|
+
? resolveIosBuildContext({ appRoot, config, target, iosSpec, exec: (cmd, runArgs) => commandOutputAt(cmd, runArgs, appRoot), assertExecutables })
|
|
217
|
+
: null;
|
|
218
|
+
const nativeIos = iosContext?.nativeIos ?? false;
|
|
219
|
+
const nativeLayout = iosContext ? iosContext.nativeLayout : resolveNativeCargoLayout({ appRoot, config });
|
|
192
220
|
if (platform === "win" && target.signingContract === "windows-raw-exe-authenticode-before-nsis-v1") {
|
|
193
221
|
assertNsisInPlaceUpgradeContract(appRoot, target.nsisUpgradeContract);
|
|
194
222
|
}
|
|
195
|
-
const { requiredInputs, buildInputs } = resolveReleaseBuildInputs({ repoRoot, appRoot, configPath, config, target });
|
|
223
|
+
const { requiredInputs, buildInputs, cargoLockPaths: pinnedCargoLocks } = resolveReleaseBuildInputs({ repoRoot, appRoot, configPath, config, target });
|
|
196
224
|
const dirtyInputs = dirtyBuildInputs(repoRoot, buildInputs, commit);
|
|
197
225
|
if (dirtyInputs.length > 0) fail(`dirty files can change the packaged app; resolve them before release:\n${dirtyInputs.map((file) => `- ${file}`).join("\n")}`);
|
|
198
|
-
const
|
|
226
|
+
const hasPackageJson = existsSync(path.join(appRoot, "package.json"));
|
|
227
|
+
// iOS records the Xcode toolchain (and xcodegen when a project.yml is named) beside the Node and Rust tools.
|
|
228
|
+
const toolVersions = isSwiftBuild ? collectSwiftToolVersions({ packageManager: config.packageManager, exec: commandOutput })
|
|
229
|
+
: iosContext ? iosContext.toolVersions
|
|
230
|
+
: collectToolVersions(config.packageManager ?? "pnpm", appRoot);
|
|
199
231
|
const inputHashes = hashInputs(requiredInputs);
|
|
200
232
|
const releaseId = `${config.app}-${config.version}-${shortCommit}`;
|
|
201
|
-
const platformDir = platform === "win" ? "windows" : platform === "android" ? "android" : "mac";
|
|
233
|
+
const platformDir = platform === "win" ? "windows" : platform === "android" ? "android" : platform === "ios" ? "ios" : "mac";
|
|
202
234
|
const stateRoot = path.join(vaultRoot, "state", releaseId, platformDir);
|
|
203
235
|
const signingIdentity = platform === "win" ? {
|
|
204
236
|
contract: target.signingContract ?? "<missing>",
|
|
@@ -230,17 +262,27 @@ try {
|
|
|
230
262
|
uploadCertSha256: androidTarget.signing.expectedCertSha256,
|
|
231
263
|
// release.mjs writes the AAB receipt into RIGHT_RELEASE_STATE_ROOT (= stateRoot) as android-aab.json.
|
|
232
264
|
receiptInputs: [path.resolve(stateRoot, ANDROID_RECEIPT_NAME)],
|
|
265
|
+
} : platform === "ios" ? {
|
|
266
|
+
contract: "ios-xcodebuild-archive-automatic-v1",
|
|
267
|
+
pipeline: IOS_PIPELINE_FINGERPRINT,
|
|
268
|
+
configSha256: hashFile(configPath),
|
|
269
|
+
// The team and bundle id the IPA receipt must carry; the upload re-checks both from the sealed manifest.
|
|
270
|
+
teamId: iosSpec.teamId,
|
|
271
|
+
bundleId: iosSpec.bundleId,
|
|
272
|
+
// release.mjs writes the IPA receipt into RIGHT_RELEASE_STATE_ROOT (= stateRoot) as ios-archive.json.
|
|
273
|
+
receiptInputs: [path.resolve(stateRoot, IOS_RECEIPT_NAME)],
|
|
233
274
|
} : null;
|
|
234
275
|
if (signingIdentity) inputHashes[".right-release/signing-identity.json"] = hashFileText(JSON.stringify(signingIdentity));
|
|
235
|
-
const cargoLock = nativeLayout
|
|
236
|
-
const cargoToml = nativeLayout
|
|
276
|
+
const cargoLock = nativeLayout?.lockPath;
|
|
277
|
+
const cargoToml = nativeLayout?.manifestPath;
|
|
237
278
|
// A broker-managed host is not identified by RIGHTKIT_BUILD_BROKER_SOCKET alone:
|
|
238
279
|
// that variable is exported by login shells only, while the managed cargo shim is
|
|
239
280
|
// on PATH for every shell. Detecting on the variable alone made agent (non-login)
|
|
240
281
|
// shells take the Cache V2 branch, bridge resolved native target into release cache,
|
|
241
282
|
// and then lose CARGO_TARGET_DIR to the broker's unconditional override — so the
|
|
242
283
|
// app bundle landed in the broker workspace and packaging failed on an empty cache.
|
|
243
|
-
|
|
284
|
+
// swiftPlan is the "no Rust host" plan for SwiftPM and for a Cargo-less native iOS app alike: same shape, same skips.
|
|
285
|
+
const swiftPlan = isSwiftBuild ? planSwiftBuild({ appRoot, vaultRoot, platform, target, toolVersions }) : nativeIos ? planIosNativeBuild({ appRoot, vaultRoot, platform, toolVersions }) : null;
|
|
244
286
|
// SwiftPM targets have no Cargo manifest: no broker cargo target, no Rust cache identity.
|
|
245
287
|
const managedCargoTarget = swiftPlan ? swiftPlan.managedCargoTarget : brokerManagedHost(process.env) ? resolveTargetRoot(cargoToml) : null;
|
|
246
288
|
const cacheIdentity = swiftPlan ? swiftPlan.identity : resolveSharedCacheIdentity({ cargoLockPath: cargoLock, cargoTomlPath: cargoToml, rustcVerbose: toolVersions.rustc, targetTriple: target.cargoTarget ?? target.targetTriple ?? target.rustTarget ?? toolVersions.rustHost, profile: target.profile ?? "release" });
|
|
@@ -267,7 +309,7 @@ try {
|
|
|
267
309
|
// Preflight BEFORE any compile. These preconditions used to be discovered one
|
|
268
310
|
// per build cycle, minutes deep, with errors that named a symptom instead of a
|
|
269
311
|
// cause. Blocking problems stop here, in seconds, naming the fix.
|
|
270
|
-
const preflight = buildPreflight({ config, configPath, appRoot, repoRoot: layout.repoRoot, platform, nativeLayout, target });
|
|
312
|
+
const preflight = buildPreflight({ config, configPath, appRoot, repoRoot: layout.repoRoot, platform, nativeLayout, target, pinnedCargoLocks });
|
|
271
313
|
const preflightBlockers = preflightFailures(preflight);
|
|
272
314
|
if (preflightBlockers.length > 0) {
|
|
273
315
|
fail(`preflight found ${preflightBlockers.length} blocking problem(s):\n${formatPreflight(preflightBlockers)}`);
|
|
@@ -323,6 +365,7 @@ try {
|
|
|
323
365
|
env.RIGHT_RELEASE_ARTIFACT_DIR = managedArtifactDir;
|
|
324
366
|
const buildRoot = managedCargoTarget ? null : path.join(vaultRoot, "build", config.app, config.version, shortCommit, platformDir);
|
|
325
367
|
env.RIGHT_RELEASE_STATE_ROOT = stateRoot;
|
|
368
|
+
env.RIGHT_RELEASE_VAULT_STATE_ROOT = path.join(vaultRoot, "state");
|
|
326
369
|
const lockPayload = {
|
|
327
370
|
schema: 1,
|
|
328
371
|
releaseId,
|
|
@@ -336,7 +379,7 @@ try {
|
|
|
336
379
|
tools: toolVersions,
|
|
337
380
|
createdAt: new Date().toISOString(),
|
|
338
381
|
};
|
|
339
|
-
const targetLink = nativeLayout
|
|
382
|
+
const targetLink = nativeLayout?.targetLink;
|
|
340
383
|
// ownedRoot is the per-app cache parent holding every fingerprint dir, so a
|
|
341
384
|
// link left by an earlier fingerprint is recognised as ours and repointed
|
|
342
385
|
// instead of hard-stopping the build.
|
|
@@ -367,7 +410,7 @@ try {
|
|
|
367
410
|
const volumes = inspectWriteVolumes({ repoRoot, vaultRoot, cacheRoot: sharedCacheRoot });
|
|
368
411
|
assertWriteVolumeFloors({ volumes, policy, configuredRepoMinimumBytes: Math.max(Number(target.preflight?.minFreeGb ?? 25), 25) * 1024 ** 3 });
|
|
369
412
|
} else assertDiskSpace(repoRoot, target.preflight?.minFreeGb ?? 20);
|
|
370
|
-
assertExecutables([...requiredBuildExecutables({ projectType: resolveProjectType({ appRoot, config, target }), packageManager: config.packageManager ?? "pnpm", usesPackageManager: Boolean(config.packageManager) ||
|
|
413
|
+
assertExecutables([...requiredBuildExecutables({ projectType: resolveProjectType({ appRoot, config, target }), packageManager: config.packageManager ?? "pnpm", usesPackageManager: nativeIos ? hasPackageJson : Boolean(config.packageManager) || hasPackageJson, usesCargo: !nativeIos }), ...(iosContext?.prepareExecutables ?? []), ...(target.preflight?.executables ?? [])]);
|
|
371
414
|
for (const name of target.preflight?.env ?? []) if (!process.env[name]) fail(`missing required environment variable: ${name}`);
|
|
372
415
|
for (const command of target.preflight?.commands ?? []) runChecked(command.cmd, command.args ?? [], path.resolve(appRoot, command.cwd ?? "."), env);
|
|
373
416
|
if (buildRoot) mkdirSync(buildRoot, { recursive: true });
|
|
@@ -394,7 +437,8 @@ try {
|
|
|
394
437
|
if (linked.action === "refused") fail(linked.reason);
|
|
395
438
|
if (linked.action === "moved-aside") console.error(`[right-release] hosted CI: moved existing ${targetLink} aside to ${linked.aside}`);
|
|
396
439
|
}
|
|
397
|
-
|
|
440
|
+
// A Cargo-less native iOS app installs Node dependencies only when it has a package.json.
|
|
441
|
+
if (nativeIos ? hasPackageJson : resolveProjectType({ appRoot, config, target }) !== "swiftpm" || config.packageManager) await runProgress(config.packageManager ?? "pnpm", ["install", "--frozen-lockfile"], appRoot, env, cacheTarget, path.join(stateRoot, "stall-install.log"));
|
|
398
442
|
},
|
|
399
443
|
build: async () => {
|
|
400
444
|
throwIfInterrupted();
|
|
@@ -409,7 +453,10 @@ try {
|
|
|
409
453
|
);
|
|
410
454
|
checkpoint(stateRoot, "build_complete");
|
|
411
455
|
checkpoint(stateRoot, "signed");
|
|
412
|
-
checkpoint(
|
|
456
|
+
// Truthful checkpoint: the worker (release.mjs) runs the hardening scan only when it has scan
|
|
457
|
+
// targets and fails the whole build when the scan fails, so reaching here with targets means
|
|
458
|
+
// the scan passed. With no targets the worker ran no scan — never claim "hardened".
|
|
459
|
+
if (hardeningScanTargets(target).length > 0) checkpoint(stateRoot, "hardened");
|
|
413
460
|
},
|
|
414
461
|
// Product-specific checks that must run after the signed/hardened bytes
|
|
415
462
|
// exist but before they become immutable. This is intentionally a small
|
|
@@ -427,7 +474,7 @@ try {
|
|
|
427
474
|
});
|
|
428
475
|
},
|
|
429
476
|
seal: async ({ sealedDir }) => {
|
|
430
|
-
sealRelease({ configRoot: appRoot, managedCargoTarget, androidTarget, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence, qaAdmission, managedRoot, stateRoot });
|
|
477
|
+
sealRelease({ configRoot: appRoot, managedCargoTarget, androidTarget, iosSpec, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence, qaAdmission, managedRoot, stateRoot });
|
|
431
478
|
verifySealedRelease(sealedDir);
|
|
432
479
|
if (cacheMode === "shared") markCacheEntrySuccessful({ layout: sharedLayout });
|
|
433
480
|
checkpoint(stateRoot, "sealed");
|
|
@@ -449,7 +496,14 @@ try {
|
|
|
449
496
|
}
|
|
450
497
|
|
|
451
498
|
function hashInputs(files) {
|
|
452
|
-
|
|
499
|
+
const hashes = {};
|
|
500
|
+
for (const file of files) {
|
|
501
|
+
const key = path.relative(path.dirname(path.dirname(file)), file);
|
|
502
|
+
// Two inputs (an app file and a shared crate's file, say) must never share a key: one would silently replace the other.
|
|
503
|
+
if (Object.hasOwn(hashes, key)) fail(`release inputs share the hash key ${key}; rename one so every input is sealed`);
|
|
504
|
+
hashes[key] = hashFile(file);
|
|
505
|
+
}
|
|
506
|
+
return hashes;
|
|
453
507
|
}
|
|
454
508
|
|
|
455
509
|
function hashFile(file) {
|
|
@@ -467,7 +521,7 @@ function sqlCipherFeatures(text) {
|
|
|
467
521
|
.filter((value) => /sqlcipher|openssl/i.test(value));
|
|
468
522
|
}
|
|
469
523
|
|
|
470
|
-
function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence, qaAdmission, managedRoot, stateRoot }) {
|
|
524
|
+
function sealRelease({ configRoot, managedCargoTarget, androidTarget, iosSpec, nativeLayout, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity, ownershipEvidence, qaAdmission, managedRoot, stateRoot }) {
|
|
471
525
|
// Every app's right-release.config.mjs names installer/updater artifacts
|
|
472
526
|
// relative to its resolved native target. On a broker-managed host that
|
|
473
527
|
// directory is never populated (the broker owns CARGO_TARGET_DIR and
|
|
@@ -476,6 +530,7 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
476
530
|
// own target root instead, or every managed build fails at seal after a
|
|
477
531
|
// full compile, sign, and notarize.
|
|
478
532
|
const targetPrefix = "src-tauri/target/";
|
|
533
|
+
// A Cargo-less iOS app has no native layout and no managed cargo target: only the default prefix is ever compared.
|
|
479
534
|
const effectiveTargetPrefix = nativeLayout?.targetPrefix ?? targetPrefix;
|
|
480
535
|
const resolveArtifactPath = (file) => {
|
|
481
536
|
const normalized = file.replaceAll(path.win32.sep, "/");
|
|
@@ -507,14 +562,28 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
507
562
|
// updater block: `target.artifacts` entries (paths or { file }) seal with the
|
|
508
563
|
// "portable" role. On android the declared AAB seals with the "aab" role instead.
|
|
509
564
|
const aabSource = platform === "android" ? resolveArtifactPath(androidTarget.aab.file) : null;
|
|
565
|
+
const ipaSource = platform === "ios" ? resolveArtifactPath(iosSpec.ipa) : null;
|
|
510
566
|
for (const artifact of target.artifacts ?? []) {
|
|
511
567
|
const file = typeof artifact === "string" ? artifact : artifact?.file;
|
|
512
568
|
if (typeof file !== "string" || !file) fail("target.artifacts entries must be paths or { file }");
|
|
513
569
|
const source = resolveArtifactPath(file);
|
|
514
|
-
addSource(source, source === aabSource ? "aab" : "portable");
|
|
570
|
+
addSource(source, source === aabSource ? "aab" : source === ipaSource ? "ipa" : "portable");
|
|
515
571
|
}
|
|
516
572
|
if (sources.size === 0) fail(`nothing to seal: declare installer.artifacts, updater.artifacts or artifacts for the ${platform} target`);
|
|
517
573
|
for (const file of sources.keys()) if (!existsSync(file)) fail(`cannot seal missing artifact: ${file}`);
|
|
574
|
+
// Every minisign updater requires an explicit trust root & full-byte signature
|
|
575
|
+
// verification before any seal directory or artifact copy is created.
|
|
576
|
+
const updaterPublicKey = config.distribution?.updaterPublicKey ?? config.distribution?.updaterPubkey ?? target?.updater?.pubkey ?? target?.updater?.publicKey;
|
|
577
|
+
if ((target.updater?.artifacts?.length ?? 0) > 0 && target.updater?.kind !== "appinstaller") {
|
|
578
|
+
if (typeof updaterPublicKey !== "string" || !updaterPublicKey.trim()) fail("updater public key is required to verify updater artifacts before sealing");
|
|
579
|
+
for (const artifact of target.updater?.artifacts ?? []) {
|
|
580
|
+
const source = resolveArtifactPath(artifact.file);
|
|
581
|
+
const signature = readFileSync(resolveArtifactPath(artifact.signature), "utf8").trim();
|
|
582
|
+
if (!signature) fail(`updater signature is empty, refusing to seal: ${artifact.signature}`);
|
|
583
|
+
try { assertUpdaterSignature(readFileSync(source), signature, updaterPublicKey, `signature of ${path.basename(artifact.file)}`); }
|
|
584
|
+
catch (error) { fail(`updater signature rejected at seal: ${error.message}`); }
|
|
585
|
+
}
|
|
586
|
+
}
|
|
518
587
|
// Android: the build receipt must describe exactly the AAB being sealed (bytes re-hashed) and its signer must be the pinned
|
|
519
588
|
// upload certificate. Checked before anything is copied, so a mismatch leaves no partial seal.
|
|
520
589
|
let androidReceipt = null;
|
|
@@ -525,6 +594,16 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
525
594
|
try { androidReceipt = verifyAndroidReceipt(receiptPath, aabSource); } catch (error) { fail(`android AAB receipt rejected: ${error.message}`); }
|
|
526
595
|
if (androidReceipt.signer?.certSha256 !== signingIdentity.uploadCertSha256) fail("android AAB receipt signer certificate is not the pinned upload certificate");
|
|
527
596
|
}
|
|
597
|
+
// iOS: the receipt must describe exactly the IPA being sealed (bytes re-hashed), for the pinned team and bundle id.
|
|
598
|
+
let iosReceipt = null;
|
|
599
|
+
if (platform === "ios") {
|
|
600
|
+
if (!sources.has(ipaSource)) fail("ios seal requires targets.ios.artifacts to list iosPackage.ipa");
|
|
601
|
+
const [receiptPath] = signingIdentity.receiptInputs;
|
|
602
|
+
if (!existsSync(receiptPath)) fail(`no signing receipts found to seal; expected at least ${receiptPath}`);
|
|
603
|
+
try { iosReceipt = verifyIosReceipt(receiptPath, ipaSource); } catch (error) { fail(`ios IPA receipt rejected: ${error.message}`); }
|
|
604
|
+
if (iosReceipt.teamId !== signingIdentity.teamId || iosReceipt.bundleId !== signingIdentity.bundleId) fail("ios IPA receipt team or bundle id is not the one pinned by the release config");
|
|
605
|
+
if (iosReceipt.sourceCommit !== commit) fail(`ios IPA receipt was built from ${String(iosReceipt.sourceCommit)}, not the release commit ${commit}`);
|
|
606
|
+
}
|
|
528
607
|
// rightkit-ffmpeg decode sidecar: release.mjs signed the binaries with the app and wrote media-sidecar.json. The seal
|
|
529
608
|
// refuses a release without it, re-checks its shape against the configuration, and (Windows) that every sidecar's
|
|
530
609
|
// signed digest is one the sealed raw-EXE signing receipt recorded. The hashes then live in the sealed manifest.
|
|
@@ -557,6 +636,8 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
557
636
|
copyFileSync(source, path.join(temp, name));
|
|
558
637
|
files.push(androidReceipt && source === aabSource && name === androidReceipt.file.name
|
|
559
638
|
? androidSealEntry(androidReceipt)
|
|
639
|
+
: iosReceipt && source === ipaSource && name === iosReceipt.file.name
|
|
640
|
+
? iosSealEntry(iosReceipt)
|
|
560
641
|
: { role: [...roles].sort().join("+"), name, sha256: hashFile(source), sizeBytes: statSync(source).size });
|
|
561
642
|
artifactInputs.push({ source, name });
|
|
562
643
|
}
|
|
@@ -575,7 +656,12 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
575
656
|
routes.patch.push({ role: "updater", name: sourceNames.get(source), signature: signatureSource ? sourceNames.get(signatureSource) : undefined, bucket: "public", key: patchKey, platform: artifact.platform });
|
|
576
657
|
routes.update.push({ role: "updater", name: sourceNames.get(source), signature: signatureSource ? sourceNames.get(signatureSource) : undefined, bucket: "private", key: routeKey, platform: artifact.platform });
|
|
577
658
|
}
|
|
578
|
-
|
|
659
|
+
// Derived from the worker's actual scan, never asserted unconditionally: release.mjs scans exactly
|
|
660
|
+
// `target.hardening ?? target.artifacts` and fails the build on scan failure, so a completed build with
|
|
661
|
+
// targets passed; with no targets no scan ran and the status is "skipped", not passed.
|
|
662
|
+
const hardeningTargets = hardeningScanTargets(target);
|
|
663
|
+
const hardeningScanned = hardeningTargets.length > 0;
|
|
664
|
+
writeJson(path.join(temp, "hardening.json"), { schema: 1, status: hardeningScanned ? "passed" : "skipped", passed: hardeningScanned, checkedAt: new Date().toISOString(), targets: hardeningTargets.length, files: hardeningScanned ? files.map((file) => file.sha256) : [] });
|
|
579
665
|
const manifest = {
|
|
580
666
|
schema: 1,
|
|
581
667
|
releaseId,
|
|
@@ -606,7 +692,7 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
606
692
|
routes,
|
|
607
693
|
inputs: inputHashes,
|
|
608
694
|
tools: toolVersions,
|
|
609
|
-
checkpoints: ["preflight_complete", "build_complete", "signed", "hardened", "sealed"],
|
|
695
|
+
checkpoints: ["preflight_complete", "build_complete", "signed", ...(hardeningScanned ? ["hardened"] : []), "sealed"],
|
|
610
696
|
sealedAt: new Date().toISOString(),
|
|
611
697
|
};
|
|
612
698
|
writeJson(path.join(temp, "release-manifest.json"), manifest);
|
|
@@ -627,21 +713,26 @@ function sealRelease({ configRoot, managedCargoTarget, androidTarget, nativeLayo
|
|
|
627
713
|
else if (retention.removed.length) console.log(`[right-release] artifact retention removed ${retention.removed.join(", ")}`);
|
|
628
714
|
}
|
|
629
715
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
716
|
+
/** The exact scan-target expression the worker uses (release.mjs: `target.hardening ?? target.artifacts ?? []`).
|
|
717
|
+
* Recipe expansion in the worker preserves both fields (`...rest`), so the raw config target gives the same list. */
|
|
718
|
+
function hardeningScanTargets(target) {
|
|
719
|
+
return target?.hardening ?? target?.artifacts ?? [];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Probes run with cwd = the app root (bogusyogi/rightkit#783): on an agent host `cargo` is the
|
|
723
|
+
// RustRight shim, which refuses any cwd outside a primary checkout, so a $TMPDIR probe dir failed
|
|
724
|
+
// with RK_NOT_PRIMARY_CHECKOUT before the first build step. The app root also makes rustc report
|
|
725
|
+
// the toolchain the app's rust-toolchain.toml selects, which is the one the build uses.
|
|
726
|
+
function collectToolVersions(packageManager, probeDir = process.cwd()) {
|
|
727
|
+
const rustcVerbose = commandOutputAt("rustc", ["-vV"], probeDir);
|
|
728
|
+
return {
|
|
729
|
+
node: process.version,
|
|
730
|
+
packageManager: `${packageManager} ${commandOutput(packageManager, ["--version"])}`,
|
|
731
|
+
cargo: commandOutputAt("cargo", ["--version"], probeDir),
|
|
732
|
+
rustc: rustcVerbose,
|
|
733
|
+
rustHost: rustcVerbose.match(/^host:\s*(.+)$/m)?.[1] ?? "unknown",
|
|
734
|
+
sccache: commandExists("sccache") ? commandOutput("sccache", ["--version"]) : null,
|
|
735
|
+
};
|
|
645
736
|
}
|
|
646
737
|
|
|
647
738
|
async function runProgress(cmd, runArgs, cwd, env, watchDir, diagnosticPath = null, { livenessOwnerTimeoutMs = null } = {}) {
|
|
@@ -801,14 +892,14 @@ function commandOutput(cmd, runArgs) {
|
|
|
801
892
|
}
|
|
802
893
|
}
|
|
803
894
|
|
|
804
|
-
function git(cwd, runArgs) {
|
|
805
|
-
return commandOutputAt("git", runArgs, cwd);
|
|
895
|
+
function git(cwd, runArgs, { trim = true } = {}) {
|
|
896
|
+
return commandOutputAt("git", runArgs, cwd, { trim });
|
|
806
897
|
}
|
|
807
898
|
|
|
808
|
-
function commandOutputAt(cmd, runArgs, cwd) {
|
|
899
|
+
function commandOutputAt(cmd, runArgs, cwd, { trim = true } = {}) {
|
|
809
900
|
const result = spawnSync(cmd, runArgs, { cwd, encoding: "utf8", windowsHide: true });
|
|
810
901
|
if (result.status !== 0) fail(`${cmd} ${runArgs.join(" ")} failed: ${(result.stderr ?? "").trim()}`);
|
|
811
|
-
return result.stdout.trim();
|
|
902
|
+
return trim ? result.stdout.trim() : result.stdout;
|
|
812
903
|
}
|
|
813
904
|
|
|
814
905
|
function runChecked(cmd, runArgs, cwd, env = process.env) {
|
|
@@ -843,7 +934,7 @@ function writeJson(file, value) {
|
|
|
843
934
|
}
|
|
844
935
|
|
|
845
936
|
function usage(code) {
|
|
846
|
-
console.log("usage: right-release build --platform win|mac|android [--config right-release.config.mjs] [--dry-run]");
|
|
937
|
+
console.log("usage: right-release build --platform win|mac|android|ios [--config right-release.config.mjs] [--dry-run]");
|
|
847
938
|
process.exit(code);
|
|
848
939
|
}
|
|
849
940
|
|
package/cli/right-release.mjs
CHANGED
|
@@ -22,10 +22,17 @@ const commands = new Map([
|
|
|
22
22
|
["sign-candidate", "sign-candidate.mjs"],
|
|
23
23
|
["engine-locate", "engine-locator.mjs"],
|
|
24
24
|
["stage-sidecar", "stage-sidecar-command.mjs"],
|
|
25
|
+
["init", "init-command.mjs"],
|
|
26
|
+
["pe", "pe-content.mjs"],
|
|
27
|
+
["icons", "icon-pack.mjs"],
|
|
25
28
|
["recipe-build", "recipe-build.mjs"],
|
|
26
29
|
["signing-agent", "signing-agent-command.mjs"],
|
|
27
30
|
["create-mac-updater", "create-mac-updater.mjs"],
|
|
28
31
|
["mirror-root-artifact", "mirror-root-artifact.mjs"],
|
|
32
|
+
["test", "test-command.mjs"],
|
|
33
|
+
["web-build", "web-build.mjs"],
|
|
34
|
+
["license-notices", "npm-license-notices.mjs"],
|
|
35
|
+
["toolchain", "toolchain-swift.mjs"],
|
|
29
36
|
["github", "github-release.mjs"],
|
|
30
37
|
["cargo-target", "cargo-target.mjs"],
|
|
31
38
|
["qa", "release-qa-command.mjs"],
|
|
@@ -35,8 +42,11 @@ const commands = new Map([
|
|
|
35
42
|
["ownership-mandatory", "ownership-mandatory-set.mjs"],
|
|
36
43
|
["swift-package", "swift-package-command.mjs"],
|
|
37
44
|
["bundle-scan", "runtime-bundle-scan.mjs"],
|
|
45
|
+
["check-release-dependencies", "release-dependencies-command.mjs"],
|
|
46
|
+
["dist-scan", "dist-scan.mjs"],
|
|
38
47
|
["identity-scan", "packaged-app-identity.mjs"],
|
|
39
48
|
["apple-profile-create", "apple-profile-create.mjs"],
|
|
49
|
+
["ios-credentials", "ios-credentials-command.mjs"],
|
|
40
50
|
]);
|
|
41
51
|
|
|
42
52
|
const args = process.argv.slice(2);
|
|
@@ -47,7 +57,7 @@ if (first === "dev") {
|
|
|
47
57
|
process.exitCode = 1;
|
|
48
58
|
});
|
|
49
59
|
} else {
|
|
50
|
-
const githubBuildCommands = new Set(["release", "build", "package", "swift-package", "sign-windows", "sign-updater", "create-mac-updater", "cargo-target"]);
|
|
60
|
+
const githubBuildCommands = new Set(["release", "build", "package", "swift-package", "sign-windows", "sign-updater", "create-mac-updater", "cargo-target", "web-build", "test"]);
|
|
51
61
|
|
|
52
62
|
try {
|
|
53
63
|
if (githubBuildCommands.has(first)
|
|
@@ -124,6 +134,8 @@ if (first === "--version" || first === "-v") {
|
|
|
124
134
|
runBinary("bash", [path.join(packageRoot, "lsclean.sh"), ...args.slice(1)]);
|
|
125
135
|
} else if (first === "generate-dmg-background") {
|
|
126
136
|
runBinary("python3", [path.join(packageRoot, "generate-dmg-background.py"), ...args.slice(1)]);
|
|
137
|
+
} else if (first === "build" && args.includes("--web")) {
|
|
138
|
+
run("web-build.mjs", args.slice(1).filter((arg) => arg !== "--web"));
|
|
127
139
|
} else if (commands.has(first)) {
|
|
128
140
|
run(commands.get(first), args.slice(1));
|
|
129
141
|
} else if (first.startsWith("-")) {
|
|
@@ -183,11 +195,15 @@ function printHelp() {
|
|
|
183
195
|
console.log(`right-release <command> [options]
|
|
184
196
|
|
|
185
197
|
Commands:
|
|
198
|
+
pe digest <file> [--algorithm sha256|sha384|sha512] [--json] | pe imports <file> [--allow dll,...] [--allow-file <path>] Signature-independent PE content digest; import-closure check
|
|
199
|
+
icons ico|icns --out <file> --frame <PNG>... [--dir <folder>] Pack hand-tuned per-size PNG frames into an ICO or ICNS
|
|
200
|
+
init --name <Product Name> --id <reverse.dns.bundle.id> [--slug <lowercase>] [--dir <root>] [--dry-run] Rename the desktop app template into a real app, in place
|
|
186
201
|
stage-sidecar [--config <file>] [--target <triple>] [--json] Stage declarative sidecars into src-tauri/binaries (Tauri externalBin naming)
|
|
187
202
|
recipe-build --plan <json> | --plan-file <path> [--app-root <dir>] Run a package recipe: prepare steps, the Tauri build, postBuild steps (argv, no shell, fail closed)
|
|
188
203
|
dmg build --app <.app> --out <dmg> --volname <name> [--background <png>] [--spec <appdmg.json>]
|
|
189
204
|
[--icon-size N] [--app-position x,y] [--applications-position x,y] [--window w,h] [--window-position x,y]
|
|
190
|
-
|
|
205
|
+
[--sign [--identifier <codesign-identifier>] [--receipt <path>]]
|
|
206
|
+
Build branded UDZO image without Finder; --sign then Developer ID signs the image (secure timestamp, no hardened runtime; never notarizes)
|
|
191
207
|
install-app --dmg <path.dmg> [--app-name Pulse.app] [--bundle-id <id>] [--dest /Applications]
|
|
192
208
|
[--relaunch|--no-relaunch] [--json]
|
|
193
209
|
Verify a signed DMG, quit, replace & relaunch hidden; no UI, no osascript (macOS)
|
|
@@ -196,12 +212,21 @@ Commands:
|
|
|
196
212
|
Windows: run the installer in place at the fixed folder; payload-only rollback (never the whole folder, user data beside the app is never copied)
|
|
197
213
|
build [--platform mac|win] [--skip-checks] [--resume-from <phase>]
|
|
198
214
|
Resume macOS at prepackage, sign, package, post-sign, or harden
|
|
215
|
+
build --web [--config <file>] [--dry-run] Web frontend build only (config web.build steps); no Tauri, signing or packaging (alias: web-build)
|
|
216
|
+
test [--config <file>] [--suite <name>[,<name>]]... [--runner <name>] [--list] [--json] [-- <args>]
|
|
217
|
+
Run the app's declared test suites (config tests.suites / tests.runners) in order; stops at the first failure
|
|
218
|
+
license-notices npm [--config <file>] [--output <file>] [--product-name <name>] [--check]
|
|
219
|
+
Write the third-party npm license appendix HTML from pnpm licenses (config legal.npmNotices); --check only compares
|
|
220
|
+
toolchain swift --version <x.y.z> --sha256 <pin> [--url <https url>] [--github-env] [--json]
|
|
221
|
+
Windows: install the pinned swift.org Swift toolchain (sha256 verified before it runs), check swift --version, find the runtime bin; --github-env exports what the installer changed to GITHUB_ENV / GITHUB_PATH
|
|
199
222
|
dev --platform win|mac [--config <path>] [--dry-run] [--build-only]
|
|
200
223
|
Incremental unsigned build & optional local install
|
|
201
224
|
swift-package <windows-stage|msix|mac-app> --config <swift-package.json> [--dry-run] [--no-sign] [--sign] [--notarize] [--profile <name>]
|
|
202
225
|
Stage SwiftPM app; build+sign MSIX (Windows) or assemble+sign+notarize .app (macOS); never uploads
|
|
203
226
|
bundle-scan [--allow-name <basename>]... <bundle-root>...
|
|
204
227
|
Fail when a bundle carries Node/Python runtimes or script sources
|
|
228
|
+
dist-scan --dist <dir> --forbid <string>... [--forbid-file <path>] [--forbid-name <regex>]... [--ext js,css,...]
|
|
229
|
+
Fail when a built web dist/ carries a forbidden marker string or mock/fixture chunk name (dev, mock or fixture code in release)
|
|
205
230
|
sign-dev-artifact --artifact <dir> --manifest <sign.json> [--metadata <payload.json>] [--evidence <path>] [--dry-run]
|
|
206
231
|
Developer ID sign an existing CI dev artifact per file with fixed identifiers; never builds, notarizes or publishes (macOS)
|
|
207
232
|
sign-candidate --platform win|mac --artifact <dir> --candidate-receipt <receipt.json> --out <binding.json> [--manifest <sign.json>] [--publisher <subject>] [--dry-run]
|
|
@@ -212,20 +237,29 @@ Commands:
|
|
|
212
237
|
Assert codesign Identifier/Team/cdhash/lipo archs; reject ad-hoc
|
|
213
238
|
upload --release <id> --platform mac|win --tier patch|update
|
|
214
239
|
Upload one sealed release; never builds
|
|
240
|
+
upload --platform ios --release <id> [--apply] Sealed IPA to TestFlight, then the exact build into the beta groups; dry run unless --apply (macOS)
|
|
241
|
+
ios-credentials store --key-id <KEYID> --issuer <uuid> | check
|
|
242
|
+
Put the App Store Connect API key (stdin), key id and issuer into the login Keychain once, or check them; never a file (macOS)
|
|
215
243
|
qa --stage source|installed --platform mac|win [--release <id>] Run rightkit-qa and write the sealed QA receipt (a missing receipt warns and is recorded; it never blocks)
|
|
216
244
|
qa audit [--app <dir>] [--json] [--since <YYYY-MM-DD>] List source/installed QA status per release; exit 1 if an uploaded build lacks passing QA
|
|
217
245
|
publish-update --release <id> --platform mac|win --tier patch|update
|
|
218
246
|
Upload sealed bytes to R2 & register RightApps
|
|
219
247
|
release Compatibility alias for build
|
|
220
248
|
publish Compatibility alias for upload
|
|
249
|
+
check-release-dependencies [--platform mac|win|linux | --all-targets] Check app-forbidden release crates
|
|
221
250
|
publish npm --package @rightkit/<name> [--dry-run] Gate preflight for npm publish
|
|
222
251
|
publish npm --package @rightkit/<name> --publish --tarball <gated.tgz>
|
|
223
252
|
Publish the gated tarball with the Keychain token
|
|
224
253
|
(npm-rightkit-publish) and verify registry bytes
|
|
225
254
|
publish cargo --crate <name> [--dry-run] Test, inspect, scan, and publish one crates.io package
|
|
226
255
|
publish npm --package @rightkit/<name> --local Mac-only local publish under the committed local-build grant
|
|
227
|
-
publish cargo --crate <name> --local (.rightkit-local-build.json): runs the unit's CI tests, packs,
|
|
228
|
-
|
|
256
|
+
publish cargo --crate <name> --local (.rightkit-local-build.json), on the Mac or Windows: runs the unit's CI tests, packs,
|
|
257
|
+
writes darwin-only (Mac) or win32-only (Windows) evidence, then the same
|
|
258
|
+
publication gate and publisher; refuses without the grant
|
|
259
|
+
publish cargo --crate <name> --local --qualify-only --bundle <new-dir> [--jobs <1-64>]
|
|
260
|
+
Verify on source host; export crate bytes & gate evidence without uploading
|
|
261
|
+
publish cargo --crate <name> --from-evidence <gate.json>
|
|
262
|
+
Reuse exact HEAD/artifact proof on upload host; recheck archive hash & registry bytes
|
|
229
263
|
publish swift [--dry-run] [--evidence <file>] [--workspace <dir>]
|
|
230
264
|
Check main, a clean tree, and the publication evidence, then tag RightKitSwift at its VERSION on origin
|
|
231
265
|
inventory check [--json] [--warn-only] [--md <file>] [--no-cache]
|
|
@@ -268,7 +302,8 @@ Commands:
|
|
|
268
302
|
lsclean <AppName.app> Clear macOS LaunchServices duplicates
|
|
269
303
|
generate-dmg-background <options> Generate the branded multi-resolution DMG background
|
|
270
304
|
mirror-root-artifact --file <path> --package-root <dir>
|
|
271
|
-
|
|
305
|
+
mirror-root-artifact --config <file> --platform mac|win [--package-root <dir>]
|
|
306
|
+
Persist artifact(s) in RIGHT_RELEASE_ARTIFACT_DIR (config form: targets.<platform>.mirror entries, optional pruneSuffix)
|
|
272
307
|
github --release <id> --tier patch|update --platform mac|win [--config <file>] [--repo owner/repo] [--dry-run]
|
|
273
308
|
Same checks and publisher as upload (explicit tier, github-releases provider + configured repository)
|
|
274
309
|
github --addon <config> --platform mac|win [--repo owner/repo] [--dry-run]
|
package/codesign-environment.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { createHash } from "node:crypto";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { detectMacSigningSession } from "./macos-signing-session.mjs";
|
|
6
6
|
import { ensurePrivateDirectory, privatePath } from "./rightkit-signing-agent.mjs";
|
|
7
|
-
import { resolveManagedRoot } from "./artifact-storage.mjs";
|
|
7
|
+
import { canonicalManagedRoot, resolveManagedRoot } from "./artifact-storage.mjs";
|
|
8
8
|
|
|
9
9
|
const quote = (text) => `'${String(text).replaceAll("'", "'\\''")}'`;
|
|
10
10
|
export function codesignShimScript({ route, node = process.execPath,
|
|
@@ -35,7 +35,7 @@ fi
|
|
|
35
35
|
exec /bin/bash "$@"\n`;
|
|
36
36
|
// Immutable wrappers avoid races between GUI & SSH builds or package versions.
|
|
37
37
|
const identity = createHash("sha256").update(script).update(automationScript).update(bashScript).digest("hex").slice(0, 24);
|
|
38
|
-
const bin = path.join(resolveManagedRoot({ env }), "signing", "codesign-bin", identity);
|
|
38
|
+
const bin = path.join(canonicalManagedRoot(resolveManagedRoot({ env })), "signing", "codesign-bin", identity);
|
|
39
39
|
ensurePrivateDirectory(bin, { io });
|
|
40
40
|
const file = path.join(bin, "codesign");
|
|
41
41
|
if (io.existsSync(file)) {
|