Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/checkout-root-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stll/oxlint-config": patch
---

Allow reviewed local actions at a checkout destination and in directory-only sparse selections while preserving version-file containment.
41 changes: 41 additions & 0 deletions packages/oxlint-config/src/toolchain-checkout-sources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,44 @@ test("directory-only sparse patterns never prove a version file", () => {
).toBe(true);
}
});

test("an action at the checkout root is valid without treating its directory as a version file", () => {
const selector = "snapshot";
const entry = { ...declaration, selector, destination: selector };
const source = { ...checkout, with: { ...checkout.with, path: selector } };
expect(
proveCheckoutSource({
declaration: entry,
checkoutSha: sha,
selector,
workflow: workflow([source, { ...consumer, uses: "./snapshot" }]),
}),
).toBe(true);
expect(
proveCheckoutSource({
declaration: entry,
checkoutSha: sha,
selector,
workflow: workflow([
source,
{ ...consumer, uses: "actions/setup-node@" + sha },
]),
}),
).toBe(false);
});

test("directory-only sparse equality covers a local action directory", () => {
expect(
prove([
{
...checkout,
with: {
...checkout.with,
"sparse-checkout": `${declaration.selector}/`,
"sparse-checkout-cone-mode": false,
},
},
consumer,
]),
).toBe(true);
});
11 changes: 10 additions & 1 deletion packages/oxlint-config/src/toolchain-checkout-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from "node:path";

import {
checkoutActionRef,
checkoutContains,
literalSparseCheckoutPath,
normalizeCheckoutPath,
selfCheckoutRepository,
Expand Down Expand Up @@ -219,7 +220,14 @@ export const proveCheckoutSource = ({
return false;
const selected = normalizeCheckoutPath(selector);
const prefix = normalizeCheckoutPath(declaration.destination);
if (prefix !== "." && !selected.startsWith(`${prefix}/`)) return false;
if (!checkoutContains({ destination: prefix, selector: selected }))
return false;
const uses = consumer["uses"];
const localAction =
typeof uses === "string" &&
uses.startsWith("./") &&
normalizeCheckoutPath(uses.slice(2)) === selected;
if (selected === prefix && !localAction) return false;
for (const step of steps.slice(checkoutIndex + 1, consumerIndex)) {
if (!record(step) || checkoutActionRef(step["uses"]) === undefined)
continue;
Expand Down Expand Up @@ -267,6 +275,7 @@ export const proveCheckoutSource = ({
!patterns.some(
(value) =>
relative === value ||
(localAction && value === `${relative}/`) ||
relative.startsWith(value.endsWith("/") ? value : `${value}/`),
)
)
Expand Down
9 changes: 9 additions & 0 deletions packages/oxlint-config/src/toolchain-checkout-syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ export const sparseCheckoutPaths = (value: string) =>
.split(/\r?\n/)
.map((entry) => entry.trim().replace(/^\//, ""))
.filter((entry) => entry !== "");

type CheckoutContainment = { destination: string; selector: string };
export const checkoutContains = ({
destination,
selector,
}: CheckoutContainment) =>
destination === "." ||
selector === destination ||
selector.startsWith(`${destination}/`);
8 changes: 2 additions & 6 deletions packages/oxlint-config/src/toolchain-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "./toolchain-checkout-sources";
import {
checkoutActionRef,
checkoutContains,
literalSparseCheckoutPath,
normalizeCheckoutPath,
selfCheckoutRepository,
Expand Down Expand Up @@ -1123,12 +1124,7 @@ export const checkRuntimeFile = ({
if (selector === undefined) return checkoutBindings;
let deepest: CheckoutBinding | undefined;
for (const entry of checkoutBindings) {
if (
entry.path !== "." &&
selector !== entry.path &&
!selector.startsWith(`${entry.path}/`)
)
continue;
if (!checkoutContains({ destination: entry.path, selector })) continue;
if (
deepest === undefined ||
(entry.path !== "." &&
Expand Down
Loading