@rightkit/git 0.2.15 → 0.2.17

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 CHANGED
@@ -38,6 +38,6 @@ Node builtins only; no runtime dependencies.
38
38
 
39
39
  ## Profiles
40
40
 
41
- `profile` defaults to `node`. `rust-hybrid` requires `ci`, accepts optional `release-candidate`, installs pinned Node plus Rust, then runs product-owned package scripts. Candidate configuration is `{ buildScript, checkScript, artifactRoot, os, targets?, signWindows?, signMacos?, releaseChain? }`. Legacy `os` remains supported & derives one target per runner; explicit `targets` is `{ os, platform, architecture }[]`, independent of main CI matrix. Each target maps to native runner, Rust target triple, exact unsigned artifact, & platform signing matrix. Supported platform values are `windows`, `macos`, `linux`; architectures are `x86_64`, `arm64`. RightGit always uploads an unsigned handoff. `signWindows: true` adds Azure OIDC signing. `signMacos: true` adds Developer ID signing plus notarization from protected environment secrets.
41
+ `profile` defaults to `node`. `rust-hybrid` requires `ci`, accepts optional `release-candidate`, installs pinned Node plus Rust, then runs product-owned package scripts. `rustCache.workspaces` declares every independent Cargo workspace whose `target` directory participates in dependency caching. Rust-hybrid workflows also enable source-aware sccache compilation caching, reuse one stable cache namespace across CI & candidate jobs, & keep workspace-crate artifacts out of rust-cache to prevent stale exact hits. Candidate configuration is `{ buildScript, checkScript, artifactRoot, os, targets?, signWindows?, signMacos?, releaseChain? }`. Legacy `os` remains supported & derives one target per runner; explicit `targets` is `{ os, platform, architecture }[]`, independent of main CI matrix. Each target maps to native runner, Rust target triple, exact unsigned artifact, & platform signing matrix. Supported platform values are `windows`, `macos`, `linux`; architectures are `x86_64`, `arm64`. RightGit always uploads an unsigned handoff. `signWindows: true` adds Azure OIDC signing. `signMacos: true` adds Developer ID signing plus notarization from protected environment secrets.
42
42
 
43
43
  `releaseChain` lets RightGit own full protected DAG while product scripts own artifact semantics: `{ windowsFinalizeScript, macosFinalizeScript?, installedQualificationScript, publishScript, qualificationRunnerVariable, publishSecrets? }`. Generated ordering is unsigned candidate → platform signing/finalization → installed qualification → publication. It deliberately rejects more than one Windows target or more than one macOS target because its installed-qualification/publish handoff has one canonical signed artifact per platform. Every protected job is tag-gated, publication needs successful installed qualification plus macOS finalization when enabled, & only publish job receives declared publication secrets plus `contents: write`.
package/manifest.mjs CHANGED
@@ -57,9 +57,11 @@ const TOP_LEVEL_KEYS = new Set([
57
57
  "templateVersion",
58
58
  "profile",
59
59
  "gate",
60
+ "rustCache",
60
61
  "candidate",
61
62
  ]);
62
63
  const GATE_PATH_RE = /^(?!.*\.\.)(?!.*\s)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
64
+ const RUST_WORKSPACE_PATH_RE = /^(?:\.|(?!.*\.\.)(?!.*\s)[A-Za-z0-9][A-Za-z0-9._/-]*)$/;
63
65
  const PACKAGE_SCRIPT_RE = /^[A-Za-z0-9][A-Za-z0-9:_-]*$/;
64
66
  const GITHUB_VARIABLE_RE = /^[A-Z][A-Z0-9_]*$/;
65
67
  const RELEASE_PLATFORMS = new Set(["windows", "macos", "linux"]);
@@ -153,6 +155,26 @@ export function validateManifest(manifest) {
153
155
  errors.push("gate is only valid for profile 'rust-hybrid'");
154
156
  }
155
157
 
158
+ const rustCache = manifest.rustCache;
159
+ if (rustCache !== undefined) {
160
+ if (profile !== "rust-hybrid") {
161
+ errors.push("rustCache is only valid for profile 'rust-hybrid'");
162
+ } else if (rustCache === null || typeof rustCache !== "object" || Array.isArray(rustCache)) {
163
+ errors.push("rustCache must be an object");
164
+ } else {
165
+ for (const key of Object.keys(rustCache)) {
166
+ if (key !== "workspaces") errors.push(`unknown rustCache field: ${key}`);
167
+ }
168
+ if (!Array.isArray(rustCache.workspaces) || rustCache.workspaces.length === 0) {
169
+ errors.push("rustCache.workspaces must be a non-empty array");
170
+ } else if (rustCache.workspaces.some((value) => typeof value !== "string" || !RUST_WORKSPACE_PATH_RE.test(value))) {
171
+ errors.push("rustCache.workspaces entries must be safe relative paths");
172
+ } else if (new Set(rustCache.workspaces).size !== rustCache.workspaces.length) {
173
+ errors.push("rustCache.workspaces entries must be unique");
174
+ }
175
+ }
176
+ }
177
+
156
178
  const candidate = manifest.candidate;
157
179
  if (candidate !== undefined) {
158
180
  if (candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
@@ -657,6 +679,7 @@ ${pm.setup}
657
679
  - name: Run installed-artifact qualification
658
680
  run: ${pm.runPrefix} ${chain.installedQualificationScript}
659
681
  env:
682
+ GH_TOKEN: \${{ github.token }}
660
683
  RIGHT_GIT_FINALIZED_WINDOWS_ROOT: \${{ runner.temp }}/right-git-release/windows
661
684
  RIGHT_GIT_QUALIFICATION_EVIDENCE_ROOT: \${{ runner.temp }}/right-git-release/qualification
662
685
  RIGHT_GIT_SOURCE_REVISION: \${{ env.RIGHT_GIT_SOURCE_REVISION }}
@@ -732,6 +755,7 @@ export function buildRenderContext(manifest, repoPath) {
732
755
  const pm = parsePackageManager(manifest.packageManager);
733
756
  const projectName = deriveProjectName(repoPath);
734
757
  const artifactPrefix = projectName.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
758
+ const rustWorkspaces = manifest.rustCache?.workspaces ?? ["engine"];
735
759
 
736
760
  const context = {
737
761
  OS_MATRIX: yamlFlowStringList(manifest.matrix.os),
@@ -746,6 +770,7 @@ export function buildRenderContext(manifest, repoPath) {
746
770
  ARTIFACT_PREFIX: artifactPrefix,
747
771
  TEMPLATE_VERSION: manifest.templateVersion,
748
772
  GATE_PATH: manifest.gate?.path,
773
+ RUST_CACHE_WORKSPACES: rustWorkspaces.map((workspace) => ` ${workspace} -> target`).join("\n"),
749
774
  CANDIDATE_BUILD_SCRIPT: manifest.candidate?.buildScript,
750
775
  CANDIDATE_CHECK_SCRIPT: manifest.candidate?.checkScript,
751
776
  CANDIDATE_ARTIFACT_ROOT: manifest.candidate?.artifactRoot,
@@ -82,6 +82,19 @@
82
82
  "pattern": "^\\d+\\.\\d+\\.\\d+$"
83
83
  }
84
84
  ,"profile": { "enum": ["node", "rust-hybrid"] },
85
+ "rustCache": {
86
+ "type": "object",
87
+ "required": ["workspaces"],
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "workspaces": {
91
+ "type": "array",
92
+ "minItems": 1,
93
+ "uniqueItems": true,
94
+ "items": { "type": "string", "pattern": "^(?:\\.|(?!.*\\.\\.)(?!.*\\s)[A-Za-z0-9][A-Za-z0-9._/-]*)$" }
95
+ }
96
+ }
97
+ },
85
98
  "gate": {
86
99
  "type": "object", "required": ["path"], "additionalProperties": false,
87
100
  "properties": { "path": { "type": "string", "pattern": "^(?!.*\\.\\.)(?!.*\\s)[A-Za-z0-9][A-Za-z0-9._/-]*$" } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/git",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Public-repo-only GitHub Actions workflow layer: templates, visibility gate, sync/drift/adopt/uninstall for right-git.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
package/render.test.mjs CHANGED
@@ -69,6 +69,8 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
69
69
  );
70
70
  assert.match(content, /pnpm run ci:candidate/);
71
71
  assert.match(content, /pnpm run ci:candidate-check/);
72
+ assert.match(content, /Mozilla-Actions\/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba/);
73
+ assert.match(content, /shared-key: widgets-rust/);
72
74
  assert.doesNotMatch(content, /bash \.\/scripts\/gate\.sh/);
73
75
  assert.match(content, /RIGHT_GIT_ARTIFACT_ROOT/);
74
76
  assert.match(content, /os: "macos-latest"/);
@@ -78,6 +80,25 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
78
80
  assert.doesNotMatch(content, /secrets\.|AZURE_|APPLE_|certificate|private.key/i);
79
81
  });
80
82
 
83
+ test("rust-hybrid cache contract renders every declared Cargo workspace", () => {
84
+ const manifest = {
85
+ ...FULL_MANIFEST,
86
+ lanes: ["ci"],
87
+ profile: "rust-hybrid",
88
+ gate: { path: "scripts/gate.sh" },
89
+ rustCache: { workspaces: [".", "apps/studio/src-tauri"] },
90
+ matrix: { os: ["windows-2025"], node: ["22.23.2"] },
91
+ };
92
+ const content = renderTemplate(
93
+ fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/ci.yml"), "utf8"),
94
+ buildRenderContext(manifest, "/repos/widgets"),
95
+ );
96
+ assert.match(content, /workspaces: \|\n \. -> target\n apps\/studio\/src-tauri -> target/);
97
+ assert.match(content, /add-job-id-key: false/);
98
+ assert.match(content, /cache-workspace-crates: false/);
99
+ assert.match(content, /RUSTC_WRAPPER: "sccache"/);
100
+ });
101
+
81
102
  test("rust-hybrid release candidate can opt into OIDC Windows signing without long-lived secrets", () => {
82
103
  const manifest = {
83
104
  ...FULL_MANIFEST,
@@ -214,6 +235,7 @@ test("rust-hybrid release chain preserves signed candidate, installed qualificat
214
235
  assert.match(content, /shell: pwsh\n run: \|\n throw 'installed_qualification_failed \{ reason: prerequisites_absent, detail: "same-run Windows signing\/finalization did not succeed" \}'/);
215
236
  assert.doesNotMatch(content, /^ run: throw 'installed_qualification_failed .*detail: /m);
216
237
  assert.match(content, /pnpm run release:qualify:installed/);
238
+ assert.match(content, /installed-qualification:[\s\S]*GH_TOKEN: \$\{\{ github\.token \}\}/);
217
239
  assert.match(content, /publish:\n needs: \[installed-qualification, macos-sign\]/);
218
240
  assert.match(content, /publish:[\s\S]*startsWith\(github\.ref, 'refs\/tags\/v'\)/);
219
241
  assert.doesNotMatch(content.slice(content.indexOf(" publish:")), /inputs\.signed_qualification/);
@@ -16,6 +16,9 @@ jobs:
16
16
  run: exit 1
17
17
  ci:
18
18
  needs: [private-repo-guard]
19
+ env:
20
+ SCCACHE_GHA_ENABLED: "true"
21
+ RUSTC_WRAPPER: "sccache"
19
22
  strategy:
20
23
  fail-fast: false
21
24
  matrix:
@@ -37,5 +40,13 @@ ${PACKAGE_MANAGER_SETUP}
37
40
  components: rustfmt, clippy
38
41
  - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
39
42
  with:
40
- workspaces: engine
43
+ workspaces: |
44
+ ${RUST_CACHE_WORKSPACES}
45
+ shared-key: ${ARTIFACT_PREFIX}-rust
46
+ add-job-id-key: false
47
+ cache-workspace-crates: false
48
+ save-if: ${{ github.event_name != 'pull_request' }}
49
+ - uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
50
+ with:
51
+ version: "v0.16.0"
41
52
  - run: bash ./${GATE_PATH}
@@ -24,6 +24,9 @@ jobs:
24
24
  run: exit 1
25
25
  candidate:
26
26
  needs: [private-repo-guard]
27
+ env:
28
+ SCCACHE_GHA_ENABLED: "true"
29
+ RUSTC_WRAPPER: "sccache"
27
30
  strategy:
28
31
  fail-fast: false
29
32
  matrix:
@@ -43,7 +46,15 @@ ${PACKAGE_MANAGER_SETUP}
43
46
  targets: ${{ matrix.targetTriple }}
44
47
  - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
45
48
  with:
46
- workspaces: engine
49
+ workspaces: |
50
+ ${RUST_CACHE_WORKSPACES}
51
+ shared-key: ${ARTIFACT_PREFIX}-rust
52
+ add-job-id-key: false
53
+ cache-workspace-crates: false
54
+ save-if: true
55
+ - uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
56
+ with:
57
+ version: "v0.16.0"
47
58
  - run: ${INSTALL_COMMAND}
48
59
  - name: Build unsigned release candidate
49
60
  run: ${PM_RUN} ${CANDIDATE_BUILD_SCRIPT}