@rightkit/git 0.2.28 → 0.2.30

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
@@ -37,6 +37,20 @@ Set `qaEvidencePath` in `.rightgit.json` to upload the gate's QA output as a wor
37
37
  - `qaEvidenceRetentionDays` is an integer from 1 to 90 and defaults to 7. It requires `qaEvidencePath`.
38
38
  - Omit the key and the rendered `ci.yml` is unchanged.
39
39
 
40
+ ## Full validation on every OS (rust-hybrid)
41
+
42
+ Set `"ciAllOs": true` to run the rust-hybrid `ci` gate on every `matrix.os` runner instead of one. Runners run in declared order with `fail-fast: false`. Each leg uploads its own artifact, because the QA artifact name already includes the runner label.
43
+
44
+ ```json
45
+ {
46
+ "profile": "rust-hybrid",
47
+ "matrix": { "os": ["xcode-27", "windows-2025"], "node": ["26.8.1"] },
48
+ "ciAllOs": true
49
+ }
50
+ ```
51
+
52
+ Omit the key, or set it to `false`, and the rendered `ci.yml` keeps one runner, preferring the first `windows-*` entry. `ciAllOs: true` is rejected for other profiles.
53
+
40
54
  ## Entry points
41
55
 
42
56
  No importable API. The package has no `main` and no `exports` map. The supported surface is the `right-git` bin.
package/manifest.mjs CHANGED
@@ -61,6 +61,7 @@ const TOP_LEVEL_KEYS = new Set([
61
61
  "rustCache",
62
62
  "qaEvidencePath",
63
63
  "qaEvidenceRetentionDays",
64
+ "ciAllOs",
64
65
  "candidate",
65
66
  "ownership",
66
67
  ]);
@@ -186,6 +187,16 @@ export function validateManifest(manifest) {
186
187
  }
187
188
  }
188
189
 
190
+ // ciAllOs (rust-hybrid ci only): run the gate on every matrix.os runner
191
+ // instead of one declared runner. Omitted or false keeps the single-runner ci job.
192
+ if (manifest.ciAllOs !== undefined) {
193
+ if (typeof manifest.ciAllOs !== "boolean") {
194
+ errors.push("ciAllOs must be a boolean");
195
+ } else if (manifest.ciAllOs === true && profile !== "rust-hybrid") {
196
+ errors.push("ciAllOs is only valid for profile 'rust-hybrid'");
197
+ }
198
+ }
199
+
189
200
  const rustCache = manifest.rustCache;
190
201
  if (rustCache !== undefined) {
191
202
  if (profile !== "rust-hybrid") {
@@ -1021,13 +1032,15 @@ export function ownershipStep(manifest, lane = "ci") {
1021
1032
  const command = name === "pnpm" ? "pnpm exec right-release ownership --app ."
1022
1033
  : name === "npm" ? "npm exec -- right-release ownership --app ."
1023
1034
  : "yarn exec right-release ownership --app .";
1035
+ // CI stays advisory until ownership approval keys are enrolled; release-candidate stays strict.
1036
+ const ownershipCommand = lane === "ci" ? `${command} --advisory-when-pending` : command;
1024
1037
  // Only release-candidate dispatches declare source_revision. Referencing
1025
1038
  // inputs.source_revision from other lanes makes actionlint reject their
1026
1039
  // generated workflows, even when guarded by an event-name expression.
1027
1040
  const sourceRevision = lane === "release-candidate"
1028
1041
  ? "\${{ inputs.source_revision }}"
1029
1042
  : "\${{ github.sha }}";
1030
- return `\n - name: RightKit ownership (protected registry roots)\n shell: bash\n run: test "$(git rev-parse HEAD)" = "${sourceRevision}" && ${command}`;
1043
+ return `\n - name: RightKit ownership (protected registry roots)\n shell: bash\n run: test "$(git rev-parse HEAD)" = "${sourceRevision}" && ${ownershipCommand}`;
1031
1044
  }
1032
1045
 
1033
1046
  /**
@@ -1058,10 +1071,20 @@ export function buildRenderContext(manifest, repoPath, lane = "ci") {
1058
1071
  const rustWorkspaces = manifest.rustCache?.workspaces ?? ["engine"];
1059
1072
  const configuredWindowsRunner = manifest.matrix.os.find((runner) => runner.startsWith("windows-"));
1060
1073
  const ciOs = configuredWindowsRunner ?? manifest.matrix.os[0];
1074
+ const ciMatrixOs = manifest.ciAllOs === true
1075
+ ? [
1076
+ " # ciAllOs: the gate runs on every matrix.os runner, in declared order.",
1077
+ ` os: ${yamlFlowStringList(manifest.matrix.os)}`,
1078
+ ]
1079
+ : [
1080
+ " # Full validation stays on one declared runner; prefer configured",
1081
+ " # Windows when available, otherwise preserve first configured OS.",
1082
+ ` os: ${yamlFlowStringList([ciOs])}`,
1083
+ ];
1061
1084
 
1062
1085
  const context = {
1063
1086
  OS_MATRIX: yamlFlowStringList(manifest.matrix.os),
1064
- CI_OS_MATRIX: yamlFlowStringList([ciOs]),
1087
+ CI_MATRIX_OS: ciMatrixOs.join("\n"),
1065
1088
  NODE_MATRIX: yamlFlowStringList(manifest.matrix.node),
1066
1089
  NODE_PUBLISH_VERSION: JSON.stringify(String(manifest.matrix.node[0])),
1067
1090
  PM_NAME: pm.name,
@@ -128,6 +128,11 @@
128
128
  "default": 7,
129
129
  "description": "rust-hybrid only, requires qaEvidencePath. Artifact retention in days."
130
130
  },
131
+ "ciAllOs": {
132
+ "type": "boolean",
133
+ "default": false,
134
+ "description": "rust-hybrid only. When true, the ci job runs the gate on every matrix.os runner in declared order (fail-fast: false). When false or unset, the ci job uses one runner (preferring the first windows-* entry, else the first matrix.os entry)."
135
+ },
131
136
  "candidate": {
132
137
  "type": "object",
133
138
  "required": ["buildScript", "checkScript", "admissionScript", "stageSummaryScript", "artifactRoot", "os"],
package/manifest.test.mjs CHANGED
@@ -406,3 +406,15 @@ test("qaEvidencePath and qaEvidenceRetentionDays are optional, rust-hybrid only,
406
406
  assert.equal(validateManifest({ ...valid, qaEvidenceRetentionDays: 7 }).valid, false, "retention requires a path");
407
407
  assert.equal(validateManifest({ ...VALID_MINIMAL, qaEvidencePath: "pulse-hub-qa" }).valid, false, "node profile rejects qaEvidencePath");
408
408
  });
409
+
410
+ test("ciAllOs is an optional boolean, rust-hybrid only", () => {
411
+ const valid = { ...VALID_MINIMAL, profile: "rust-hybrid", gate: { path: "scripts/gate.sh" } };
412
+ assert.equal(validateManifest(valid).valid, true);
413
+ assert.equal(validateManifest({ ...valid, ciAllOs: true }).valid, true);
414
+ assert.equal(validateManifest({ ...valid, ciAllOs: false }).valid, true);
415
+ for (const ciAllOs of ["true", 1, null]) {
416
+ assert.equal(validateManifest({ ...valid, ciAllOs }).valid, false, String(ciAllOs));
417
+ }
418
+ assert.equal(validateManifest({ ...VALID_MINIMAL, ciAllOs: true }).valid, false, "node profile rejects ciAllOs true");
419
+ assert.equal(validateManifest({ ...VALID_MINIMAL, ciAllOs: false }).valid, true, "false is accepted for any profile");
420
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/git",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
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
@@ -119,6 +119,35 @@ test("rust-hybrid ci preserves a declared macOS-only lane", () => {
119
119
  assert.doesNotMatch(content, /windows-2025/);
120
120
  });
121
121
 
122
+ test("rust-hybrid ci runs every matrix.os runner when ciAllOs is true, in declared order", () => {
123
+ const base = {
124
+ ...FULL_MANIFEST,
125
+ lanes: ["ci"],
126
+ profile: "rust-hybrid",
127
+ gate: { path: "scripts/gate.sh" },
128
+ qaEvidencePath: "pulse-hub-qa",
129
+ matrix: { os: ["xcode-27", "windows-2025"], node: ["26.8.1"] },
130
+ };
131
+ const render = (manifest) => renderTemplate(
132
+ fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/ci.yml"), "utf8"),
133
+ buildRenderContext(manifest, "/repos/pulse"),
134
+ );
135
+ const all = render({ ...base, ciAllOs: true });
136
+ assert.match(all, /^ {8}os: \["xcode-27", "windows-2025"\]$/m);
137
+ assert.match(all, /runs-on: \$\{\{ matrix\.os \}\}/);
138
+ assert.match(all, /fail-fast: false/);
139
+ assert.match(all, /name: qa-evidence-\$\{\{ matrix\.os \|\| runner\.os \}\}-\$\{\{ github\.run_attempt \}\}/);
140
+ assert.doesNotMatch(all, /Full validation stays/);
141
+ // ciAllOs false and unset render the same single-runner matrix as before.
142
+ for (const manifest of [base, { ...base, ciAllOs: false }]) {
143
+ const single = render(manifest);
144
+ assert.match(single, /# Full validation stays on one declared runner; prefer configured\n {8}# Windows when available, otherwise preserve first configured OS\.\n {8}os: \["windows-2025"\]\n/);
145
+ assert.doesNotMatch(single, /xcode-27/);
146
+ }
147
+ // Unset and false produce byte-identical output.
148
+ assert.equal(render({ ...base, ciAllOs: false }), render(base));
149
+ });
150
+
122
151
  test("rust-hybrid release candidate renders product scripts, artifact handoff, and no signing secret", () => {
123
152
  const manifest = {
124
153
  ...FULL_MANIFEST,
@@ -22,9 +22,7 @@ jobs:
22
22
  strategy:
23
23
  fail-fast: false
24
24
  matrix:
25
- # Full validation stays on one declared runner; prefer configured
26
- # Windows when available, otherwise preserve first configured OS.
27
- os: ${CI_OS_MATRIX}
25
+ ${CI_MATRIX_OS}
28
26
  node: ${NODE_MATRIX}
29
27
  runs-on: ${{ matrix.os }}
30
28
  steps: