@rightkit/git 0.2.25 → 0.2.26
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/guard-coverage.test.mjs +1 -2
- package/manifest.mjs +10 -4
- package/package.json +1 -1
- package/render.test.mjs +23 -14
- package/sync.mjs +1 -1
- package/templates/profiles/rust-hybrid/ci.yml +1 -1
package/guard-coverage.test.mjs
CHANGED
|
@@ -166,12 +166,11 @@ function isGuarded(jobs, jobName, visiting = new Set()) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
function renderAllLanes() {
|
|
169
|
-
const context = buildRenderContext(FULL_MANIFEST, "/repos/widgets");
|
|
170
169
|
const rendered = {};
|
|
171
170
|
for (const lane of FULL_MANIFEST.lanes) {
|
|
172
171
|
const templatePath = path.join(templatesDir, LANE_FILENAMES[lane]);
|
|
173
172
|
const templateText = fs.readFileSync(templatePath, "utf8");
|
|
174
|
-
rendered[lane] = renderTemplate(templateText,
|
|
173
|
+
rendered[lane] = renderTemplate(templateText, buildRenderContext(FULL_MANIFEST, "/repos/widgets", lane));
|
|
175
174
|
}
|
|
176
175
|
return rendered;
|
|
177
176
|
}
|
package/manifest.mjs
CHANGED
|
@@ -990,15 +990,21 @@ export function deriveProjectName(repoPath) {
|
|
|
990
990
|
* validated manifest plus the target repo's derived project name.
|
|
991
991
|
*/
|
|
992
992
|
/** Every managed public workflow runs ownership scanning; repository metadata cannot opt it out. */
|
|
993
|
-
export function ownershipStep(manifest) {
|
|
993
|
+
export function ownershipStep(manifest, lane = "ci") {
|
|
994
994
|
const { name } = parsePackageManager(manifest.packageManager);
|
|
995
995
|
const command = name === "pnpm" ? "pnpm exec right-release ownership --app ."
|
|
996
996
|
: name === "npm" ? "npm exec -- right-release ownership --app ."
|
|
997
997
|
: "yarn exec right-release ownership --app .";
|
|
998
|
-
|
|
998
|
+
// Only release-candidate dispatches declare source_revision. Referencing
|
|
999
|
+
// inputs.source_revision from other lanes makes actionlint reject their
|
|
1000
|
+
// generated workflows, even when guarded by an event-name expression.
|
|
1001
|
+
const sourceRevision = lane === "release-candidate"
|
|
1002
|
+
? "\${{ inputs.source_revision }}"
|
|
1003
|
+
: "\${{ github.sha }}";
|
|
1004
|
+
return `\n - name: RightKit ownership (protected registry roots)\n shell: bash\n run: test "$(git rev-parse HEAD)" = "${sourceRevision}" && ${command}`;
|
|
999
1005
|
}
|
|
1000
1006
|
|
|
1001
|
-
export function buildRenderContext(manifest, repoPath) {
|
|
1007
|
+
export function buildRenderContext(manifest, repoPath, lane = "ci") {
|
|
1002
1008
|
const pm = parsePackageManager(manifest.packageManager);
|
|
1003
1009
|
const projectName = deriveProjectName(repoPath);
|
|
1004
1010
|
const artifactPrefix = projectName.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
@@ -1024,7 +1030,7 @@ export function buildRenderContext(manifest, repoPath) {
|
|
|
1024
1030
|
WINDOWS_SIGNING_JOB: windowsSigningJob(manifest, pm, artifactPrefix),
|
|
1025
1031
|
MACOS_SIGNING_JOB: macosSigningJob(manifest, pm, artifactPrefix),
|
|
1026
1032
|
RELEASE_CHAIN_JOBS: releaseChainJobs(manifest, pm, artifactPrefix),
|
|
1027
|
-
OWNERSHIP_STEP: ownershipStep(manifest),
|
|
1033
|
+
OWNERSHIP_STEP: ownershipStep(manifest, lane),
|
|
1028
1034
|
};
|
|
1029
1035
|
|
|
1030
1036
|
// Every key below is populated ONLY when its source manifest section is
|
package/package.json
CHANGED
package/render.test.mjs
CHANGED
|
@@ -24,12 +24,11 @@ const FULL_MANIFEST = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
function renderAllLanes(manifest, repoPath) {
|
|
27
|
-
const context = buildRenderContext(manifest, repoPath);
|
|
28
27
|
const rendered = {};
|
|
29
28
|
for (const lane of manifest.lanes) {
|
|
30
29
|
const templatePath = path.join(templatesDir, LANE_FILENAMES[lane]);
|
|
31
30
|
const templateText = fs.readFileSync(templatePath, "utf8");
|
|
32
|
-
rendered[lane] = renderTemplate(templateText,
|
|
31
|
+
rendered[lane] = renderTemplate(templateText, buildRenderContext(manifest, repoPath, lane));
|
|
33
32
|
}
|
|
34
33
|
return rendered;
|
|
35
34
|
}
|
|
@@ -60,6 +59,7 @@ test("rust-hybrid ci renders its pinned package manager and gate", () => {
|
|
|
60
59
|
assert.match(content, /changed=true[\s\S]*\^0\{40\}\$[\s\S]*git cat-file -e[\s\S]*git diff --name-only --diff-filter=ACDMRTUXB/);
|
|
61
60
|
assert.match(content, /--diff-filter=ACDMRTUXB/, "deleted Rust inputs must trigger Rust setup");
|
|
62
61
|
assert.match(content, /\*\.rs\|Cargo\.toml[\s\S]*scripts\/gate\.sh/);
|
|
62
|
+
assert.doesNotMatch(content, /\|build\.rs\||\|\*\/build\.rs\|/);
|
|
63
63
|
assert.match(content, /\.rightgit\.json\|\.github\/workflows\/\*\.yml\|\.github\/workflows\/\*\.yaml/);
|
|
64
64
|
assert.equal((content.match(/if: steps\.rust-changes\.outputs\.required == 'true'/g) ?? []).length, 3);
|
|
65
65
|
assert.match(content, /RIGHT_GIT_RUST_CHANGED: \$\{\{ steps\.rust-changes\.outputs\.changed \}\}/);
|
|
@@ -94,6 +94,15 @@ test("ordinary CI templates cancel superseded runs on the same ref", () => {
|
|
|
94
94
|
assert.match(generic, /cancel-in-progress: true/);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
test("ownership revision expression matches each lane trigger contract", () => {
|
|
98
|
+
const rendered = renderAllLanes(FULL_MANIFEST, "/repos/widgets");
|
|
99
|
+
assert.match(rendered.ci, /git rev-parse HEAD\)" = "\$\{\{ github\.sha \}\}/);
|
|
100
|
+
assert.doesNotMatch(rendered.ci, /inputs\.source_revision/);
|
|
101
|
+
assert.match(rendered["publish-package-managers"], /git rev-parse HEAD\)" = "\$\{\{ github\.sha \}\}/);
|
|
102
|
+
assert.doesNotMatch(rendered["publish-package-managers"], /inputs\.source_revision/);
|
|
103
|
+
assert.match(rendered["release-candidate"], /git rev-parse HEAD\)" = "\$\{\{ inputs\.source_revision \}\}/);
|
|
104
|
+
});
|
|
105
|
+
|
|
97
106
|
test("rust-hybrid ci preserves a declared macOS-only lane", () => {
|
|
98
107
|
const manifest = {
|
|
99
108
|
...FULL_MANIFEST,
|
|
@@ -195,7 +204,7 @@ test("rust-hybrid release candidate can opt into OIDC Windows signing without lo
|
|
|
195
204
|
};
|
|
196
205
|
const content = renderTemplate(
|
|
197
206
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
198
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
207
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
199
208
|
);
|
|
200
209
|
assert.match(content, /environment: release/);
|
|
201
210
|
// Tag-last contract: signing is gated on the admitted, same-run
|
|
@@ -212,7 +221,7 @@ test("rust-hybrid release candidate can opt into OIDC Windows signing without lo
|
|
|
212
221
|
// out the exact admitted revision by SHA, never by resolving HEAD after a
|
|
213
222
|
// ref-triggered checkout.
|
|
214
223
|
assert.match(content, /windows-sign:[\s\S]*?ref: \$\{\{ needs\.admission\.outputs\.source_revision \}\}\n\s+fetch-depth: 0\n\s+persist-credentials: false/);
|
|
215
|
-
assert.match(content, /git rev-parse HEAD.*inputs\.source_revision
|
|
224
|
+
assert.match(content, /git rev-parse HEAD.*inputs\.source_revision/);
|
|
216
225
|
assert.ok(content.includes("LEGION_SOURCE_REVISION: ${{ env.RIGHT_GIT_SOURCE_REVISION }}"));
|
|
217
226
|
assert.doesNotMatch(content, /LEGION_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
218
227
|
assert.doesNotMatch(content, /secrets\.|client-secret|private.key/i);
|
|
@@ -240,7 +249,7 @@ test("rust-hybrid release candidate can opt into protected macOS signing & notar
|
|
|
240
249
|
};
|
|
241
250
|
const content = renderTemplate(
|
|
242
251
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
243
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
252
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
244
253
|
);
|
|
245
254
|
assert.match(content, /macos-sign:/);
|
|
246
255
|
assert.match(content, /macos-sign:\n needs: \[admission, candidate\]\n if: \$\{\{ needs\.admission\.outputs\.signed_qualification == 'true' \}\}/);
|
|
@@ -251,7 +260,7 @@ test("rust-hybrid release candidate can opt into protected macOS signing & notar
|
|
|
251
260
|
assert.match(content, /right-release build --platform mac --skip-checks/);
|
|
252
261
|
assert.match(content, /secrets\.APPLE_API_KEY_BASE64/);
|
|
253
262
|
assert.match(content, /macos-sign:[\s\S]*?ref: \$\{\{ needs\.admission\.outputs\.source_revision \}\}\n\s+fetch-depth: 0\n\s+persist-credentials: false/);
|
|
254
|
-
assert.match(content, /git rev-parse HEAD.*inputs\.source_revision
|
|
263
|
+
assert.match(content, /git rev-parse HEAD.*inputs\.source_revision/);
|
|
255
264
|
assert.ok(content.includes("LEGION_SOURCE_REVISION: ${{ env.RIGHT_GIT_SOURCE_REVISION }}"));
|
|
256
265
|
assert.doesNotMatch(content, /LEGION_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
257
266
|
assert.match(content, /security delete-keychain/);
|
|
@@ -287,7 +296,7 @@ test("rust-hybrid multi-architecture candidates bind every native target to exac
|
|
|
287
296
|
};
|
|
288
297
|
const content = renderTemplate(
|
|
289
298
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
290
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
299
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
291
300
|
);
|
|
292
301
|
for (const [runner, triple] of [["windows-2025", "x86_64-pc-windows-msvc"], ["windows-11-arm", "aarch64-pc-windows-msvc"], ["macos-15", "aarch64-apple-darwin"], ["macos-15-intel", "x86_64-apple-darwin"]]) {
|
|
293
302
|
assert.match(content, new RegExp(`os: ${JSON.stringify(runner).replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}`));
|
|
@@ -338,7 +347,7 @@ test("rust-hybrid release chain preserves signed candidate, installed qualificat
|
|
|
338
347
|
};
|
|
339
348
|
const content = renderTemplate(
|
|
340
349
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
341
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
350
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
342
351
|
);
|
|
343
352
|
assert.match(content, /windows-sign:[\s\S]*pnpm run release:finalize:win/);
|
|
344
353
|
assert.match(content, /macos-sign:[\s\S]*pnpm run release:finalize:mac/);
|
|
@@ -372,7 +381,7 @@ test("rust-hybrid release chain preserves signed candidate, installed qualificat
|
|
|
372
381
|
assert.match(content, /CLOUDFLARE_API_TOKEN: \$\{\{ secrets\.CLOUDFLARE_API_TOKEN \}\}/);
|
|
373
382
|
// Ownership scan binds to the source revision input before admission emits
|
|
374
383
|
// that same revision for every downstream job.
|
|
375
|
-
assert.match(content, /git rev-parse HEAD.*inputs\.source_revision
|
|
384
|
+
assert.match(content, /git rev-parse HEAD.*inputs\.source_revision/);
|
|
376
385
|
assert.doesNotMatch(content, /RIGHT_GIT_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
377
386
|
// candidate, windows-sign, macos-sign, installed-qualification and publish all
|
|
378
387
|
// check out the admitted revision with full history; admission checks out
|
|
@@ -430,7 +439,7 @@ test("every 'run <script>' in a full release-chain workflow names a script the m
|
|
|
430
439
|
};
|
|
431
440
|
const content = renderTemplate(
|
|
432
441
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
433
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
442
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
434
443
|
);
|
|
435
444
|
const declaredScripts = new Set([
|
|
436
445
|
manifest.candidate.buildScript,
|
|
@@ -491,7 +500,7 @@ test("release chain: installed-qualification has a stage summary and publish dow
|
|
|
491
500
|
};
|
|
492
501
|
const content = renderTemplate(
|
|
493
502
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
494
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
503
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
495
504
|
);
|
|
496
505
|
const installedQualificationJob = content.slice(
|
|
497
506
|
content.search(/^ {2}installed-qualification:\n {4}needs:/m),
|
|
@@ -689,7 +698,7 @@ test("every job checking out an admitted revision fetches full history", () => {
|
|
|
689
698
|
};
|
|
690
699
|
const content = renderTemplate(
|
|
691
700
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
692
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
701
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
693
702
|
);
|
|
694
703
|
const checkouts = content
|
|
695
704
|
.split(/^ {6}- uses: actions\/checkout@/m)
|
|
@@ -732,7 +741,7 @@ test("release-candidate template exposes a dry_run input threaded through admiss
|
|
|
732
741
|
};
|
|
733
742
|
const content = renderTemplate(
|
|
734
743
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
735
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
744
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
736
745
|
);
|
|
737
746
|
assert.match(content, /^ dry_run:\n description: .*\n required: false\n type: boolean\n default: false$/m);
|
|
738
747
|
assert.match(content, /dry_run: \$\{\{ steps\.admit\.outputs\.dry_run \}\}/);
|
|
@@ -785,7 +794,7 @@ function environmentManifest(environments) {
|
|
|
785
794
|
function renderRelease(manifest) {
|
|
786
795
|
return renderTemplate(
|
|
787
796
|
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
788
|
-
buildRenderContext(manifest, "/repos/widgets"),
|
|
797
|
+
buildRenderContext(manifest, "/repos/widgets", "release-candidate"),
|
|
789
798
|
);
|
|
790
799
|
}
|
|
791
800
|
|
package/sync.mjs
CHANGED
|
@@ -31,8 +31,8 @@ function releaseChainDependencyError(repoPath, manifest) {
|
|
|
31
31
|
|
|
32
32
|
/** Render every lane declared in the manifest. Returns { lane, filename, content }[]. */
|
|
33
33
|
export function renderLanes(manifest, repoPath) {
|
|
34
|
-
const context = buildRenderContext(manifest, repoPath);
|
|
35
34
|
return manifest.lanes.map((lane) => {
|
|
35
|
+
const context = buildRenderContext(manifest, repoPath, lane);
|
|
36
36
|
const filename = LANE_FILENAMES[lane];
|
|
37
37
|
const templatePath = manifest.profile === "rust-hybrid"
|
|
38
38
|
? path.join(TEMPLATES_DIR, "profiles", "rust-hybrid", filename)
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
changed=false
|
|
46
46
|
while IFS= read -r changed_path; do
|
|
47
47
|
case "$changed_path" in
|
|
48
|
-
*.rs|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|
|
|
48
|
+
*.rs|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|rust-toolchain|*/rust-toolchain|rust-toolchain.toml|*/rust-toolchain.toml|.cargo/*|*/.cargo/*|.rightgit.json|.github/workflows/*.yml|.github/workflows/*.yaml|${GATE_PATH})
|
|
49
49
|
changed=true
|
|
50
50
|
break
|
|
51
51
|
;;
|