@rightkit/release 0.2.146 → 0.2.148

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/build-release.mjs CHANGED
@@ -31,7 +31,7 @@ import { createTargetBridge } from "./target-bridge.mjs";
31
31
  import { signingReceiptPath } from "./signing-receipts.mjs";
32
32
  import { validateNotarizationReceipt } from "./macos-notarization.mjs";
33
33
  import { brokerManagedHost } from "./cargo-contract.mjs";
34
- import { resolveReleaseCacheMode } from "./release-cache-mode.mjs";
34
+ import { ensureLegacyTargetLink, resolveReleaseCacheMode } from "./release-cache-mode.mjs";
35
35
  import { resolveTargetRoot } from "./cargo-target.mjs";
36
36
  import { assertNsisInPlaceUpgradeContract } from "./nsis-upgrade-contract.mjs";
37
37
  import { collectSwiftToolVersions, planSwiftBuild } from "./swift-build-context.mjs";
@@ -363,9 +363,10 @@ try {
363
363
  // flag already knows to leave a real directory untouched instead.
364
364
  if (swiftPlan) { /* SwiftPM: no Rust target link */ }
365
365
  else if (cacheMode === "shared" || managedCargoTarget) targetBridge.ensure();
366
- else if (!existsSync(targetLink)) symlinkSync(cacheTarget, targetLink, process.platform === "win32" ? "junction" : "dir");
367
- else if (realpathSync(targetLink) !== realpathSync(cacheTarget)) {
368
- fail(`primary checkout target exists and is not the release cache link: ${targetLink}`);
366
+ else {
367
+ const linked = ensureLegacyTargetLink({ targetLink, cacheTarget, fs: { existsSync, realpathSync, renameSync, symlinkSync } });
368
+ if (linked.action === "refused") fail(linked.reason);
369
+ if (linked.action === "moved-aside") console.error(`[right-release] hosted CI: moved existing ${targetLink} aside to ${linked.aside}`);
369
370
  }
370
371
  if (resolveProjectType({ appRoot, config, target }) !== "swiftpm" || config.packageManager) await runProgress(config.packageManager ?? "pnpm", ["install", "--frozen-lockfile"], appRoot, env, cacheTarget, path.join(stateRoot, "stall-install.log"));
371
372
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.146",
3
+ "version": "0.2.148",
4
4
  "description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -1,3 +1,5 @@
1
+ import { isManagedHost } from "./managed-host-secrets.mjs";
2
+
1
3
  export function resolveReleaseCacheMode({ requested, platform, env = process.env } = {}) {
2
4
  const fallback = env.GITHUB_ACTIONS === "true"
3
5
  ? "legacy"
@@ -6,3 +8,22 @@ export function resolveReleaseCacheMode({ requested, platform, env = process.env
6
8
  if (mode !== "legacy" && mode !== "shared") throw new Error("RIGHT_RELEASE_CACHE_MODE must be legacy or shared");
7
9
  return mode;
8
10
  }
11
+
12
+ /**
13
+ * Legacy (non-shared, non-broker) mode links the checkout's target directory to the release cache.
14
+ * A target that already exists and points elsewhere fails closed on developer hosts, where it may hold
15
+ * work. Hosted CI runners are ephemeral: an earlier workflow step (a build or a cache restore) may have
16
+ * created a real target directory, so it is renamed aside and the link created instead of failing.
17
+ */
18
+ export function ensureLegacyTargetLink({ targetLink, cacheTarget, platform = process.platform, env = process.env, fs, now = () => Date.now() }) {
19
+ const linkType = platform === "win32" ? "junction" : "dir";
20
+ if (!fs.existsSync(targetLink)) { fs.symlinkSync(cacheTarget, targetLink, linkType); return { action: "linked" }; }
21
+ if (fs.realpathSync(targetLink) === fs.realpathSync(cacheTarget)) return { action: "current" };
22
+ if (env.GITHUB_ACTIONS !== "true" || isManagedHost(env)) {
23
+ return { action: "refused", reason: `primary checkout target exists and is not the release cache link: ${targetLink}` };
24
+ }
25
+ const aside = `${targetLink}.pre-right-release-${now()}`;
26
+ fs.renameSync(targetLink, aside);
27
+ fs.symlinkSync(cacheTarget, targetLink, linkType);
28
+ return { action: "moved-aside", aside };
29
+ }
package/release.mjs CHANGED
@@ -742,7 +742,8 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
742
742
  fail(`${appName} right-git-ci-only policy requires a valid .rightgit.json`);
743
743
  }
744
744
  const lanes = manifest?.lanes;
745
- const safeLanes = new Set(["ci", "release-candidate"]);
745
+ // Mirrors the `lanes` enum in @rightkit/git manifest.schema.json.
746
+ const safeLanes = new Set(["ci", "qualification", "package-smoke", "release-candidate", "publish-npm", "publish-package-managers"]);
746
747
  if (manifest?.schemaVersion !== 1
747
748
  || manifest?.compile !== "github-actions-only"
748
749
  || !Array.isArray(lanes)
@@ -750,7 +751,7 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
750
751
  || !lanes.includes("ci")
751
752
  || new Set(lanes).size !== lanes.length
752
753
  || lanes.some((lane) => !safeLanes.has(lane))) {
753
- fail(`${appName} right-git-ci-only policy requires compile=github-actions-only plus unique .rightgit.json lanes drawn from ["ci", "release-candidate"] and including "ci"`);
754
+ fail(`${appName} right-git-ci-only policy requires compile=github-actions-only plus unique .rightgit.json lanes drawn from [${[...safeLanes].map((lane) => `"${lane}"`).join(", ")}] and including "ci"`);
754
755
  }
755
756
  const expectedWorkflows = lanes.map((lane) => `${lane}.yml`).sort();
756
757
  if (JSON.stringify(workflows) !== JSON.stringify(expectedWorkflows)) {
@@ -798,6 +799,15 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
798
799
  const protectedMacosSigning = workflow === "release-candidate.yml"
799
800
  && manifest?.candidate?.signMacos === true
800
801
  && (legacySigningGate("macos-sign").test(macosSource) || admittedSigningGate("macos-sign").test(macosSource));
802
+ // @rightkit/git `devArtifact.mac: "sign"` renders a `dev-mac-sign` job into
803
+ // the managed ci.yml: push to the default branch only, release environment,
804
+ // Developer ID certificate secrets only (no notarization keys).
805
+ const devMacSpan = workflow === "ci.yml" ? jobSpan("dev-mac-sign") : null;
806
+ const devMacSource = devMacSpan ? source.slice(devMacSpan.start, devMacSpan.end) : "";
807
+ const allowedDevMacSecrets = new Set(["APPLE_CERTIFICATE_BASE64", "APPLE_CERTIFICATE_PASSWORD", "APPLE_KEYCHAIN_PASSWORD"]);
808
+ const protectedDevMacSigning = workflow === "ci.yml"
809
+ && manifest?.devArtifact?.mac === "sign"
810
+ && /dev-mac-sign:\n(?: #[^\n]*\n)* needs: \[ci\]\n if: \$\{\{ github\.event_name == 'push' && github\.ref_name == github\.event\.repository\.default_branch \}\}\n environment: /.test(devMacSource);
801
811
  const releaseChain = workflow === "release-candidate.yml" ? manifest?.candidate?.releaseChain : undefined;
802
812
  const allowedReleaseSecrets = new Set(releaseChain?.publishSecrets ?? []);
803
813
  const writePermissions = [...source.matchAll(/^\s*(actions|attestations|checks|contents|deployments|discussions|id-token|issues|models|packages|pages|pull-requests|security-events|statuses):\s*write\s*$/gmi)].map((match) => ({ name: match[1].toLowerCase(), index: match.index }));
@@ -821,6 +831,7 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
821
831
  || /pull_request_target\s*:/i.test(source)
822
832
  || /\bsecrets\s*\[/i.test(source)
823
833
  || requestedSecrets.some(({ name, index }) => !(protectedMacosSigning && inside(index, macosSpan) && allowedMacosSecrets.has(name))
834
+ && !(protectedDevMacSigning && inside(index, devMacSpan) && allowedDevMacSecrets.has(name))
824
835
  && !(protectedReleasePublish && inside(index, publishSpan) && allowedReleaseSecrets.has(name)))) {
825
836
  fail(`${appName} right-git managed ${workflow} requests release-capable permissions or secrets`);
826
837
  }
@@ -16,7 +16,7 @@
16
16
  "@rightkit/app-shell": "0.3.0",
17
17
  "@rightkit/ax": "0.2.4",
18
18
  "@rightkit/doc-render": "0.1.4",
19
- "@rightkit/git": "0.2.33",
19
+ "@rightkit/git": "0.2.34",
20
20
  "@rightkit/hooks": "0.1.2",
21
21
  "@rightkit/i18n": "0.1.0",
22
22
  "@rightkit/legal": "0.3.1",
@@ -29,7 +29,7 @@
29
29
  "@rightkit/platform-ui": "0.2.0",
30
30
  "@rightkit/qa": "0.4.0",
31
31
  "@rightkit/qa-chrome": "0.1.3",
32
- "@rightkit/release": "0.2.146",
32
+ "@rightkit/release": "0.2.148",
33
33
  "@rightkit/settings-ui": "0.3.0",
34
34
  "@rightkit/shell": "0.1.0",
35
35
  "@rightkit/tauri": "0.2.0",
@@ -74,7 +74,8 @@
74
74
  "0.2.29",
75
75
  "0.2.30",
76
76
  "0.2.31",
77
- "0.2.32"
77
+ "0.2.32",
78
+ "0.2.33"
78
79
  ],
79
80
  "@rightkit/legal-ui": [
80
81
  "0.1.0",
@@ -178,7 +179,9 @@
178
179
  "0.2.142",
179
180
  "0.2.143",
180
181
  "0.2.144",
181
- "0.2.145"
182
+ "0.2.145",
183
+ "0.2.146",
184
+ "0.2.147"
182
185
  ],
183
186
  "@rightkit/qa": [
184
187
  "0.1.0",
@@ -377,7 +380,7 @@
377
380
  "@rightkit/app-shell": "0.3.0",
378
381
  "@rightkit/ax": "0.2.4",
379
382
  "@rightkit/doc-render": "0.1.4",
380
- "@rightkit/git": "0.2.33",
383
+ "@rightkit/git": "0.2.34",
381
384
  "@rightkit/hooks": "0.1.2",
382
385
  "@rightkit/i18n": "0.1.0",
383
386
  "@rightkit/legal": "0.3.1",
@@ -390,7 +393,7 @@
390
393
  "@rightkit/platform-ui": "0.2.0",
391
394
  "@rightkit/qa": "0.4.0",
392
395
  "@rightkit/qa-chrome": "0.1.3",
393
- "@rightkit/release": "0.2.146",
396
+ "@rightkit/release": "0.2.147",
394
397
  "@rightkit/settings-ui": "0.3.0",
395
398
  "@rightkit/shell": "0.1.0",
396
399
  "@rightkit/tauri": "0.2.0",