@rightkit/git 0.2.21 → 0.2.23
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 -0
- package/install.mjs +2 -1
- package/manifest.mjs +9 -1
- package/manifest.schema.json +6 -2
- package/manifest.test.mjs +6 -0
- package/package.json +1 -1
- package/render.test.mjs +21 -2
- package/sync.test.mjs +1 -0
- package/templates/profiles/rust-hybrid/ci.yml +39 -3
- package/test-support.mjs +1 -0
- package/uninstall.test.mjs +11 -1
package/guard-coverage.test.mjs
CHANGED
|
@@ -52,6 +52,7 @@ const templatesDir = path.join(packageRoot, "templates");
|
|
|
52
52
|
|
|
53
53
|
const FULL_MANIFEST = {
|
|
54
54
|
schemaVersion: 1,
|
|
55
|
+
compile: "github-actions-only",
|
|
55
56
|
lanes: [...KNOWN_LANES],
|
|
56
57
|
matrix: { os: ["ubuntu-latest", "macos-latest", "windows-latest"], node: ["22.13", "24"] },
|
|
57
58
|
packageManager: "pnpm@11.24.0",
|
package/install.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { applyPlan, planSync } from "./sync.mjs";
|
|
|
13
13
|
|
|
14
14
|
export const DEFAULT_MATRIX = { os: ["ubuntu-latest", "macos-latest", "windows-latest"], node: ["22.13", "24"] };
|
|
15
15
|
export const DEFAULT_PACKAGE_MANAGER = "pnpm@11.24.0";
|
|
16
|
-
export const DEFAULT_TEMPLATE_VERSION = "1.
|
|
16
|
+
export const DEFAULT_TEMPLATE_VERSION = "1.8.0";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* opts: { lanes: string[], matrix?, packageManager?, templateVersion?,
|
|
@@ -38,6 +38,7 @@ export async function installCommand(repoPath, opts = {}, deps = {}) {
|
|
|
38
38
|
|
|
39
39
|
const manifest = {
|
|
40
40
|
schemaVersion: 1,
|
|
41
|
+
compile: "github-actions-only",
|
|
41
42
|
lanes,
|
|
42
43
|
matrix: opts.matrix || DEFAULT_MATRIX,
|
|
43
44
|
packageManager: opts.packageManager || DEFAULT_PACKAGE_MANAGER,
|
package/manifest.mjs
CHANGED
|
@@ -50,6 +50,7 @@ const WINGET_PACKAGE_IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9-]*(?:\.[A-Za-z0-9][
|
|
|
50
50
|
|
|
51
51
|
const TOP_LEVEL_KEYS = new Set([
|
|
52
52
|
"schemaVersion",
|
|
53
|
+
"compile",
|
|
53
54
|
"lanes",
|
|
54
55
|
"matrix",
|
|
55
56
|
"packageManager",
|
|
@@ -100,6 +101,9 @@ export function validateManifest(manifest) {
|
|
|
100
101
|
if (manifest.schemaVersion !== 1) {
|
|
101
102
|
errors.push("schemaVersion must be exactly 1");
|
|
102
103
|
}
|
|
104
|
+
if (manifest.compile !== "github-actions-only") {
|
|
105
|
+
errors.push("compile must be exactly 'github-actions-only' for public repositories");
|
|
106
|
+
}
|
|
103
107
|
|
|
104
108
|
let lanes = [];
|
|
105
109
|
if (!Array.isArray(manifest.lanes) || manifest.lanes.length === 0) {
|
|
@@ -163,7 +167,7 @@ export function validateManifest(manifest) {
|
|
|
163
167
|
errors.push("rustCache must be an object");
|
|
164
168
|
} else {
|
|
165
169
|
for (const key of Object.keys(rustCache)) {
|
|
166
|
-
if (key !== "workspaces") errors.push(`unknown rustCache field: ${key}`);
|
|
170
|
+
if (key !== "workspaces" && key !== "requiredForGate") errors.push(`unknown rustCache field: ${key}`);
|
|
167
171
|
}
|
|
168
172
|
if (!Array.isArray(rustCache.workspaces) || rustCache.workspaces.length === 0) {
|
|
169
173
|
errors.push("rustCache.workspaces must be a non-empty array");
|
|
@@ -172,6 +176,9 @@ export function validateManifest(manifest) {
|
|
|
172
176
|
} else if (new Set(rustCache.workspaces).size !== rustCache.workspaces.length) {
|
|
173
177
|
errors.push("rustCache.workspaces entries must be unique");
|
|
174
178
|
}
|
|
179
|
+
if (rustCache.requiredForGate !== undefined && typeof rustCache.requiredForGate !== "boolean") {
|
|
180
|
+
errors.push("rustCache.requiredForGate must be a boolean");
|
|
181
|
+
}
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -772,6 +779,7 @@ export function buildRenderContext(manifest, repoPath) {
|
|
|
772
779
|
TEMPLATE_VERSION: manifest.templateVersion,
|
|
773
780
|
GATE_PATH: manifest.gate?.path,
|
|
774
781
|
RUST_CACHE_WORKSPACES: rustWorkspaces.map((workspace) => ` ${workspace} -> target`).join("\n"),
|
|
782
|
+
RUST_REQUIRED_FOR_GATE: manifest.rustCache?.requiredForGate === true ? "true" : "false",
|
|
775
783
|
CANDIDATE_BUILD_SCRIPT: manifest.candidate?.buildScript,
|
|
776
784
|
CANDIDATE_CHECK_SCRIPT: manifest.candidate?.checkScript,
|
|
777
785
|
CANDIDATE_ARTIFACT_ROOT: manifest.candidate?.artifactRoot,
|
package/manifest.schema.json
CHANGED
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
"title": "right-git per-repo manifest (.rightgit.json)",
|
|
5
5
|
"description": "Declares which right-git lanes a public repo installs and how templates should be parameterized. Hand-validated by manifest.mjs — this file documents the contract, it is not fed to a JSON Schema validator at runtime.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": ["schemaVersion", "lanes", "matrix", "packageManager", "templateVersion"],
|
|
7
|
+
"required": ["schemaVersion", "compile", "lanes", "matrix", "packageManager", "templateVersion"],
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
10
|
"schemaVersion": {
|
|
11
11
|
"const": 1
|
|
12
12
|
},
|
|
13
|
+
"compile": {
|
|
14
|
+
"const": "github-actions-only"
|
|
15
|
+
},
|
|
13
16
|
"lanes": {
|
|
14
17
|
"type": "array",
|
|
15
18
|
"minItems": 1,
|
|
@@ -92,7 +95,8 @@
|
|
|
92
95
|
"minItems": 1,
|
|
93
96
|
"uniqueItems": true,
|
|
94
97
|
"items": { "type": "string", "pattern": "^(?:\\.|(?!.*\\.\\.)(?!.*\\s)[A-Za-z0-9][A-Za-z0-9._/-]*)$" }
|
|
95
|
-
}
|
|
98
|
+
},
|
|
99
|
+
"requiredForGate": { "type": "boolean" }
|
|
96
100
|
}
|
|
97
101
|
},
|
|
98
102
|
"gate": {
|
package/manifest.test.mjs
CHANGED
|
@@ -4,12 +4,18 @@ import { validateManifest } from "./manifest.mjs";
|
|
|
4
4
|
|
|
5
5
|
const VALID_MINIMAL = {
|
|
6
6
|
schemaVersion: 1,
|
|
7
|
+
compile: "github-actions-only",
|
|
7
8
|
lanes: ["ci"],
|
|
8
9
|
matrix: { os: ["ubuntu-latest"], node: ["22.13"] },
|
|
9
10
|
packageManager: "pnpm@11.24.0",
|
|
10
11
|
templateVersion: "1.0.0",
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
test("requires GitHub Actions only compilation", () => {
|
|
15
|
+
assert.equal(validateManifest({ ...VALID_MINIMAL, compile: undefined }).valid, false);
|
|
16
|
+
assert.equal(validateManifest({ ...VALID_MINIMAL, compile: "local" }).valid, false);
|
|
17
|
+
});
|
|
18
|
+
|
|
13
19
|
test("accepts a minimal valid manifest", () => {
|
|
14
20
|
const { valid, errors } = validateManifest(VALID_MINIMAL);
|
|
15
21
|
assert.equal(valid, true);
|
package/package.json
CHANGED
package/render.test.mjs
CHANGED
|
@@ -11,6 +11,7 @@ const templatesDir = path.join(packageRoot, "templates");
|
|
|
11
11
|
|
|
12
12
|
const FULL_MANIFEST = {
|
|
13
13
|
schemaVersion: 1,
|
|
14
|
+
compile: "github-actions-only",
|
|
14
15
|
lanes: [...KNOWN_LANES],
|
|
15
16
|
matrix: { os: ["ubuntu-latest", "macos-latest", "windows-latest"], node: ["22.13", "24"] },
|
|
16
17
|
packageManager: "pnpm@11.24.0",
|
|
@@ -54,6 +55,20 @@ test("rust-hybrid ci renders its pinned package manager and gate", () => {
|
|
|
54
55
|
assert.match(content, /group: \$\{\{ github\.workflow \}\}-\$\{\{ github\.ref \}\}/);
|
|
55
56
|
assert.match(content, /cancel-in-progress: true/);
|
|
56
57
|
assert.match(content, /bash \.\/scripts\/gate\.sh/);
|
|
58
|
+
assert.match(content, /name: Detect Rust-affecting changes\n id: rust-changes/);
|
|
59
|
+
assert.match(content, /RIGHT_GIT_DIFF_BASE: \$\{\{ github\.event_name == 'pull_request' && github\.event\.pull_request\.base\.sha \|\| github\.event\.before \}\}/);
|
|
60
|
+
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
|
+
assert.match(content, /--diff-filter=ACDMRTUXB/, "deleted Rust inputs must trigger Rust setup");
|
|
62
|
+
assert.match(content, /\*\.rs\|Cargo\.toml[\s\S]*scripts\/gate\.sh/);
|
|
63
|
+
assert.match(content, /\.rightgit\.json\|\.github\/workflows\/\*\.yml\|\.github\/workflows\/\*\.yaml/);
|
|
64
|
+
assert.equal((content.match(/if: steps\.rust-changes\.outputs\.required == 'true'/g) ?? []).length, 3);
|
|
65
|
+
assert.match(content, /RIGHT_GIT_RUST_CHANGED: \$\{\{ steps\.rust-changes\.outputs\.changed \}\}/);
|
|
66
|
+
assert.match(content, /RIGHT_GIT_RUST_REQUIRED: \$\{\{ steps\.rust-changes\.outputs\.required \}\}/);
|
|
67
|
+
assert.match(content, /RUSTC_WRAPPER: \$\{\{ steps\.rust-changes\.outputs\.required == 'true' && 'sccache' \|\| '' \}\}/);
|
|
68
|
+
assert.match(content, /CARGO_INCREMENTAL: "0"/);
|
|
69
|
+
assert.match(content, /CARGO_PROFILE_DEV_DEBUG: "0"/);
|
|
70
|
+
assert.match(content, /CARGO_PROFILE_TEST_DEBUG: "0"/);
|
|
71
|
+
assert.doesNotMatch(content, /opt-level|codegen-units|\blto\b|strip\s*=/i);
|
|
57
72
|
assert.match(content, /os: \["windows-latest"\]/);
|
|
58
73
|
assert.doesNotMatch(content, /ubuntu-latest|macos-latest/);
|
|
59
74
|
});
|
|
@@ -104,6 +119,9 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
|
|
|
104
119
|
assert.match(content, /RIGHT_GIT_RELEASE_ARCHITECTURE: \$\{\{ matrix\.architecture \}\}/);
|
|
105
120
|
assert.match(content, /actions\/upload-artifact@[a-f0-9]{40}/);
|
|
106
121
|
assert.doesNotMatch(content, /secrets\.|AZURE_|APPLE_|certificate|private.key/i);
|
|
122
|
+
assert.doesNotMatch(content, /rust-changes|RIGHT_GIT_RUST_CHANGED/);
|
|
123
|
+
assert.doesNotMatch(content, /if: steps\.rust-changes/);
|
|
124
|
+
assert.match(content, /uses: dtolnay\/rust-toolchain@[a-f0-9]{40}\n with:/);
|
|
107
125
|
});
|
|
108
126
|
|
|
109
127
|
test("rust-hybrid cache contract renders every declared Cargo workspace", () => {
|
|
@@ -112,7 +130,7 @@ test("rust-hybrid cache contract renders every declared Cargo workspace", () =>
|
|
|
112
130
|
lanes: ["ci"],
|
|
113
131
|
profile: "rust-hybrid",
|
|
114
132
|
gate: { path: "scripts/gate.sh" },
|
|
115
|
-
rustCache: { workspaces: [".", "apps/studio/src-tauri"] },
|
|
133
|
+
rustCache: { workspaces: [".", "apps/studio/src-tauri"], requiredForGate: true },
|
|
116
134
|
matrix: { os: ["windows-2025"], node: ["22.23.2"] },
|
|
117
135
|
};
|
|
118
136
|
const content = renderTemplate(
|
|
@@ -123,7 +141,8 @@ test("rust-hybrid cache contract renders every declared Cargo workspace", () =>
|
|
|
123
141
|
assert.match(content, /add-job-id-key: false/);
|
|
124
142
|
assert.match(content, /cache-workspace-crates: false/);
|
|
125
143
|
assert.match(content, /cache-on-failure: true/);
|
|
126
|
-
assert.match(content, /
|
|
144
|
+
assert.match(content, /if \[\[ "true" == "true" \]\]; then[\s\S]*required=true/);
|
|
145
|
+
assert.match(content, /RUSTC_WRAPPER: \$\{\{ steps\.rust-changes\.outputs\.required == 'true' && 'sccache' \|\| '' \}\}/);
|
|
127
146
|
});
|
|
128
147
|
|
|
129
148
|
test("rust-hybrid release candidate can opt into OIDC Windows signing without long-lived secrets", () => {
|
package/sync.test.mjs
CHANGED
|
@@ -57,6 +57,7 @@ test("sync reports manifest errors instead of writing files", async () => {
|
|
|
57
57
|
test("sync rejects a Membrane-like release chain without root @rightkit/release", async () => {
|
|
58
58
|
const manifest = {
|
|
59
59
|
schemaVersion: 1,
|
|
60
|
+
compile: "github-actions-only",
|
|
60
61
|
lanes: ["ci", "release-candidate"],
|
|
61
62
|
matrix: { os: ["windows-2025", "macos-15"], node: ["22.23.2"] },
|
|
62
63
|
packageManager: "pnpm@11.24.0",
|
|
@@ -19,9 +19,6 @@ jobs:
|
|
|
19
19
|
run: exit 1
|
|
20
20
|
ci:
|
|
21
21
|
needs: [private-repo-guard]
|
|
22
|
-
env:
|
|
23
|
-
SCCACHE_GHA_ENABLED: "true"
|
|
24
|
-
RUSTC_WRAPPER: "sccache"
|
|
25
22
|
strategy:
|
|
26
23
|
fail-fast: false
|
|
27
24
|
matrix:
|
|
@@ -34,15 +31,45 @@ jobs:
|
|
|
34
31
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
32
|
with:
|
|
36
33
|
fetch-depth: 0
|
|
34
|
+
- name: Detect Rust-affecting changes
|
|
35
|
+
id: rust-changes
|
|
36
|
+
shell: bash
|
|
37
|
+
env:
|
|
38
|
+
RIGHT_GIT_DIFF_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
|
39
|
+
run: |
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
changed=true
|
|
42
|
+
base="$RIGHT_GIT_DIFF_BASE"
|
|
43
|
+
if [[ "$base" =~ ^[0-9a-fA-F]{40}$ ]] && [[ ! "$base" =~ ^0{40}$ ]] && git cat-file -e "$base^{commit}" 2>/dev/null; then
|
|
44
|
+
if changed_paths="$(git diff --name-only --diff-filter=ACDMRTUXB "$base" "$GITHUB_SHA")"; then
|
|
45
|
+
changed=false
|
|
46
|
+
while IFS= read -r changed_path; do
|
|
47
|
+
case "$changed_path" in
|
|
48
|
+
*.rs|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|build.rs|*/build.rs|rust-toolchain|*/rust-toolchain|rust-toolchain.toml|*/rust-toolchain.toml|.cargo/*|*/.cargo/*|.rightgit.json|.github/workflows/*.yml|.github/workflows/*.yaml|${GATE_PATH})
|
|
49
|
+
changed=true
|
|
50
|
+
break
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
done <<< "$changed_paths"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
printf 'changed=%s\n' "$changed" >> "$GITHUB_OUTPUT"
|
|
57
|
+
required="$changed"
|
|
58
|
+
if [[ "${RUST_REQUIRED_FOR_GATE}" == "true" ]]; then
|
|
59
|
+
required=true
|
|
60
|
+
fi
|
|
61
|
+
printf 'required=%s\n' "$required" >> "$GITHUB_OUTPUT"
|
|
37
62
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
38
63
|
with:
|
|
39
64
|
node-version: ${{ matrix.node }}
|
|
40
65
|
package-manager-cache: false
|
|
41
66
|
${PACKAGE_MANAGER_SETUP}
|
|
42
67
|
- uses: dtolnay/rust-toolchain@f8be11a05b1d4f3fcebe6410cc16743212b999b0
|
|
68
|
+
if: steps.rust-changes.outputs.required == 'true'
|
|
43
69
|
with:
|
|
44
70
|
components: rustfmt, clippy
|
|
45
71
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
72
|
+
if: steps.rust-changes.outputs.required == 'true'
|
|
46
73
|
with:
|
|
47
74
|
workspaces: |
|
|
48
75
|
${RUST_CACHE_WORKSPACES}
|
|
@@ -52,6 +79,15 @@ ${RUST_CACHE_WORKSPACES}
|
|
|
52
79
|
cache-on-failure: true
|
|
53
80
|
save-if: ${{ github.event_name != 'pull_request' }}
|
|
54
81
|
- uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
|
82
|
+
if: steps.rust-changes.outputs.required == 'true'
|
|
55
83
|
with:
|
|
56
84
|
version: "v0.16.0"
|
|
57
85
|
- run: bash ./${GATE_PATH}
|
|
86
|
+
env:
|
|
87
|
+
RIGHT_GIT_RUST_CHANGED: ${{ steps.rust-changes.outputs.changed }}
|
|
88
|
+
RIGHT_GIT_RUST_REQUIRED: ${{ steps.rust-changes.outputs.required }}
|
|
89
|
+
SCCACHE_GHA_ENABLED: "true"
|
|
90
|
+
RUSTC_WRAPPER: ${{ steps.rust-changes.outputs.required == 'true' && 'sccache' || '' }}
|
|
91
|
+
CARGO_INCREMENTAL: "0"
|
|
92
|
+
CARGO_PROFILE_DEV_DEBUG: "0"
|
|
93
|
+
CARGO_PROFILE_TEST_DEBUG: "0"
|
package/test-support.mjs
CHANGED
|
@@ -26,6 +26,7 @@ export const PRIVATE_DEPS = ghDeps("PRIVATE");
|
|
|
26
26
|
|
|
27
27
|
export const SAMPLE_MANIFEST = {
|
|
28
28
|
schemaVersion: 1,
|
|
29
|
+
compile: "github-actions-only",
|
|
29
30
|
lanes: ["ci", "package-smoke"],
|
|
30
31
|
matrix: { os: ["ubuntu-latest"], node: ["22.13"] },
|
|
31
32
|
packageManager: "pnpm@11.24.0",
|
package/uninstall.test.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { syncCommand } from "./sync.mjs";
|
|
6
|
-
import { installCommand } from "./install.mjs";
|
|
6
|
+
import { DEFAULT_TEMPLATE_VERSION, installCommand } from "./install.mjs";
|
|
7
7
|
import { uninstallCommand } from "./uninstall.mjs";
|
|
8
8
|
import { listManagedFiles } from "./sync.mjs";
|
|
9
9
|
import { MANIFEST_FILENAME } from "./manifest.mjs";
|
|
@@ -77,3 +77,13 @@ test("install validates before it creates a manifest", async () => {
|
|
|
77
77
|
assert.equal(result.stage, "manifest-invalid");
|
|
78
78
|
assert.equal(fs.existsSync(path.join(dir, MANIFEST_FILENAME)), false);
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
test("install emits GitHub-Actions-only policy at template 1.8.0", async () => {
|
|
82
|
+
const dir = makeTempRepo(null);
|
|
83
|
+
const result = await installCommand(dir, { lanes: ["ci"] }, PUBLIC_DEPS);
|
|
84
|
+
assert.equal(result.ok, true);
|
|
85
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(dir, MANIFEST_FILENAME), "utf8"));
|
|
86
|
+
assert.equal(manifest.compile, "github-actions-only");
|
|
87
|
+
assert.equal(DEFAULT_TEMPLATE_VERSION, "1.8.0");
|
|
88
|
+
assert.equal(manifest.templateVersion, DEFAULT_TEMPLATE_VERSION);
|
|
89
|
+
});
|