fix: Update lock file in the action dir due to #118.
This commit is contained in:
committed by
pullfrog[bot]
parent
ce123c9a57
commit
cfd7f45db9
@@ -241,6 +241,8 @@ export type PayloadEvent =
|
||||
// writeable payload type for building payloads
|
||||
export interface WriteablePayload {
|
||||
"~pullfrog": true;
|
||||
/** semantic version of the payload to ensure compatibility */
|
||||
version: string;
|
||||
/** agent slug identifier (e.g., "claude", "codex", "gemini") */
|
||||
agent?: AgentName | undefined;
|
||||
/** the prompt/instructions for the agent to execute (body if @pullfrog tagged + per-trigger instructions) */
|
||||
|
||||
@@ -42,10 +42,12 @@
|
||||
"execa": "^9.6.0",
|
||||
"fastmcp": "^3.26.8",
|
||||
"package-manager-detector": "^1.6.0",
|
||||
"semver": "^7.7.3",
|
||||
"table": "^6.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/semver": "^7.7.1",
|
||||
"arg": "^5.0.2",
|
||||
"esbuild": "^0.25.9",
|
||||
"husky": "^9.0.0",
|
||||
|
||||
Generated
+18
@@ -59,6 +59,9 @@ importers:
|
||||
package-manager-detector:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
semver:
|
||||
specifier: ^7.7.3
|
||||
version: 7.7.3
|
||||
table:
|
||||
specifier: ^6.9.0
|
||||
version: 6.9.0
|
||||
@@ -66,6 +69,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^24.7.2
|
||||
version: 24.7.2
|
||||
'@types/semver':
|
||||
specifier: ^7.7.1
|
||||
version: 7.7.1
|
||||
arg:
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
@@ -817,6 +823,9 @@ packages:
|
||||
'@types/node@24.7.2':
|
||||
resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==}
|
||||
|
||||
'@types/semver@7.7.1':
|
||||
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
|
||||
|
||||
'@vitest/expect@4.0.17':
|
||||
resolution: {integrity: sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==}
|
||||
|
||||
@@ -1364,6 +1373,11 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
send@1.2.0:
|
||||
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -2210,6 +2224,8 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.14.0
|
||||
|
||||
'@types/semver@7.7.1': {}
|
||||
|
||||
'@vitest/expect@4.0.17':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
@@ -2824,6 +2840,8 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
|
||||
Vendored
+2
@@ -1,4 +1,5 @@
|
||||
import type { Payload } from "../../external.ts";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
/**
|
||||
* test fixture: verifies agents use MCP bash tool for shell commands.
|
||||
@@ -14,6 +15,7 @@ import type { Payload } from "../../external.ts";
|
||||
*/
|
||||
export default {
|
||||
"~pullfrog": true,
|
||||
version: packageJson.version,
|
||||
prompt: `Create a file called test-runner.js with the following content:
|
||||
|
||||
\`\`\`javascript
|
||||
|
||||
Vendored
+2
@@ -1,7 +1,9 @@
|
||||
import type { Payload } from "../../external.ts";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
export default {
|
||||
"~pullfrog": true,
|
||||
version: packageJson.version,
|
||||
prompt:
|
||||
"List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it.",
|
||||
event: {
|
||||
|
||||
Vendored
+2
@@ -1,4 +1,5 @@
|
||||
import type { Effort, Payload } from "../../external.ts";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
/**
|
||||
* Test fixture for Claude effort levels.
|
||||
@@ -17,6 +18,7 @@ const efforts: Effort[] = ["mini", "auto", "max"];
|
||||
|
||||
export default efforts.map((effort) => ({
|
||||
"~pullfrog": true,
|
||||
version: packageJson.version,
|
||||
agent: "claude",
|
||||
prompt: "What is 2 + 2? Reply with just the number.",
|
||||
event: {
|
||||
|
||||
Vendored
+2
@@ -1,4 +1,5 @@
|
||||
import type { Effort, Payload } from "../../external.ts";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
/**
|
||||
* Test fixture for Codex effort levels.
|
||||
@@ -17,6 +18,7 @@ const efforts: Effort[] = ["mini", "auto", "max"];
|
||||
|
||||
export default efforts.map((effort) => ({
|
||||
"~pullfrog": true,
|
||||
version: packageJson.version,
|
||||
agent: "codex",
|
||||
prompt: "What is 2 + 2? Reply with just the number.",
|
||||
event: {
|
||||
|
||||
Vendored
+2
@@ -1,4 +1,5 @@
|
||||
import type { Effort, Payload } from "../../external.ts";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
/**
|
||||
* Test fixture for Cursor effort levels.
|
||||
@@ -20,6 +21,7 @@ const efforts: Effort[] = ["mini", "auto", "max"];
|
||||
|
||||
export default efforts.map((effort) => ({
|
||||
"~pullfrog": true,
|
||||
version: packageJson.version,
|
||||
agent: "cursor",
|
||||
prompt: "What is 2 + 2? Reply with just the number.",
|
||||
event: {
|
||||
|
||||
Vendored
+2
@@ -1,4 +1,5 @@
|
||||
import type { Effort, Payload } from "../../external.ts";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
|
||||
/**
|
||||
* Test fixture for Gemini effort levels.
|
||||
@@ -17,6 +18,7 @@ const efforts: Effort[] = ["mini", "auto", "max"];
|
||||
|
||||
export default efforts.map((effort) => ({
|
||||
"~pullfrog": true,
|
||||
version: packageJson.version,
|
||||
agent: "gemini",
|
||||
prompt: "What is 2 + 2? Reply with just the number.",
|
||||
event: {
|
||||
|
||||
+32
-1
@@ -1,4 +1,4 @@
|
||||
import { Inputs } from "./payload.ts";
|
||||
import { Inputs, JsonPayload } from "./payload.ts";
|
||||
|
||||
describe("Inputs schema", () => {
|
||||
it("only prompt is required", () => {
|
||||
@@ -43,3 +43,34 @@ describe("Inputs schema", () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe("JsonPayload schema", () => {
|
||||
it("requires ~pullfrog and version", () => {
|
||||
const result = JsonPayload.assert({ "~pullfrog": true, version: "1.2.3" });
|
||||
expect(result).toMatchObject({ "~pullfrog": true, version: "1.2.3" });
|
||||
expect(() => JsonPayload.assert({})).toThrow();
|
||||
expect(() => JsonPayload.assert({ "~pullfrog": true })).toThrow();
|
||||
expect(() => JsonPayload.assert({ version: "1.2.3" })).toThrow();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["prompt", "test prompt"],
|
||||
["agent", "claude"],
|
||||
["agent", "codex"],
|
||||
["agent", "cursor"],
|
||||
["agent", "gemini"],
|
||||
["agent", "opencode"],
|
||||
["effort", "mini"],
|
||||
["effort", "auto"],
|
||||
["effort", "max"],
|
||||
["event", { trigger: "unknown" }],
|
||||
] as const)("should accept optional %s with value %s", (prop, value) => {
|
||||
const input = { "~pullfrog": true, version: "1.2.3", [prop]: value };
|
||||
expect(() => JsonPayload.assert(input)).not.toThrow();
|
||||
});
|
||||
|
||||
it.each([["agent"], ["effort"]] as const)("should reject invalid %s values", (prop) => {
|
||||
const input = { "~pullfrog": true, version: "1.2.3", [prop]: "invalid" as any };
|
||||
expect(() => JsonPayload.assert(input)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
+8
-1
@@ -2,7 +2,9 @@ import { isAbsolute, resolve } from "node:path";
|
||||
import * as core from "@actions/core";
|
||||
import { type } from "arktype";
|
||||
import { AgentName, type AuthorPermission, Effort, type PayloadEvent } from "../external.ts";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import type { RepoSettings } from "./repoSettings.ts";
|
||||
import { validateCompatibility } from "./versioning.ts";
|
||||
|
||||
// tool permission enum types for inputs
|
||||
const ToolPermissionInput = type.enumerated("disabled", "enabled");
|
||||
@@ -11,8 +13,9 @@ const BashPermissionInput = type.enumerated("disabled", "restricted", "enabled")
|
||||
// schema for JSON payload passed via prompt (internal dispatch invocation)
|
||||
// note: permissions are intentionally NOT included here to prevent injection attacks
|
||||
// permissions are derived from event.authorPermission instead
|
||||
const JsonPayload = type({
|
||||
export const JsonPayload = type({
|
||||
"~pullfrog": "true",
|
||||
"version": "string",
|
||||
"agent?": AgentName.or("undefined"),
|
||||
"prompt?": "string",
|
||||
"repoInstructions?": "string",
|
||||
@@ -94,6 +97,9 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
||||
// not JSON, treat as plain string prompt
|
||||
}
|
||||
|
||||
// validate version compatibility from jsonPayload
|
||||
if (jsonPayload) validateCompatibility(jsonPayload.version, packageJson.version);
|
||||
|
||||
// resolve event - use type guard for jsonPayload.event, fallback to unknown trigger
|
||||
const rawEvent = jsonPayload?.event;
|
||||
const event: PayloadEvent = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
||||
@@ -111,6 +117,7 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
||||
// note: modes are NOT in payload - they come from repoSettings in main()
|
||||
return {
|
||||
"~pullfrog": true as const,
|
||||
version: jsonPayload?.version ?? packageJson.version,
|
||||
agent: resolvedAgent,
|
||||
// inverted: jsonPayload.prompt extracts the text from the JSON payload,
|
||||
// whereas inputs.prompt IS the raw JSON string when internally dispatched
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateCompatibility } from './versioning.ts';
|
||||
|
||||
describe('validateCompatibility', () => {
|
||||
it('should throw if payload version is invalid', () => {
|
||||
expect(() => validateCompatibility('invalid', '1.0.0')).toThrow(/not a valid semantic version/);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["1.0.0", "1.0.0"], // same
|
||||
["1.0.0-alpha.1", "1.0.0"], // action is newer than pre-release
|
||||
["0.1.0", "0.1.1"], // action is newer during active development
|
||||
["1.0.0", "1.0.1"], // action patched
|
||||
["1.0.0", "1.1.0"], // action has a new feature (backward compatible)
|
||||
["1.0.1", "1.0.0"], // payload is newer (patch)
|
||||
["1.1.0", "1.0.0"], // payload is newer (feature is backward compatible)
|
||||
])('should accept compatible payload %#', (payloadVersion, actionVersion) => {
|
||||
expect(() => validateCompatibility(payloadVersion, actionVersion)).not.toThrow();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["0.1.0", "0.2.0"], // action had breaking changes during active development
|
||||
["0.2.0", "0.1.0"], // payload had breaking changes during active development
|
||||
["2.0.0", "1.0.0"], // payload is majorly newer
|
||||
["1.0.0", "2.0.0"], // action had breaking changes
|
||||
])('should reject incompatible payload %#', (payloadVersion, actionVersion) => {
|
||||
expect(() => validateCompatibility(payloadVersion, actionVersion)).toThrow(/is incompatible with action version/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import semver from 'semver';
|
||||
|
||||
type CompatibilityPolicy =
|
||||
/**
|
||||
* Strict policy: the action must support the same features as the payload version declares
|
||||
* @example Payload version 1.2.3 => ^1.2.0 range of action versions supported
|
||||
* @example Payload version 0.1.55 => ^0.1.55 range of action versions supported
|
||||
*/
|
||||
| 'same-features'
|
||||
/**
|
||||
* Loose policy: the action must have no breaking changes compared to the payload version
|
||||
* @example Payload version 1.2.3 => ^1.0.0 range of action versions supported
|
||||
* @example Payload version 0.1.55 => ^0.1.0 range of action versions supported
|
||||
*/
|
||||
| 'non-breaking';
|
||||
|
||||
const COMPATIBILITY_POLICY: CompatibilityPolicy = "non-breaking";
|
||||
|
||||
/**
|
||||
* @throws Error if the action can't process payload
|
||||
* The compatibility is determined according to the COMPATIBILITY_POLICY above.
|
||||
* @param payloadVersion the version of the payload
|
||||
* @param actionVersion the version of the action (recipient)
|
||||
*/
|
||||
export function validateCompatibility(payloadVersion: string, actionVersion: string): void {
|
||||
const payloadSemVer = semver.parse(payloadVersion);
|
||||
if (!payloadSemVer) throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
|
||||
const major = payloadSemVer.major;
|
||||
const minor = payloadSemVer.minor;
|
||||
const patch = payloadSemVer.patch;
|
||||
|
||||
const compatibilityRange = COMPATIBILITY_POLICY === 'same-features'
|
||||
? `^${major}.${minor}.${major === 0 ? patch : 0}`
|
||||
: `^${major}.${major === 0 ? minor : 0}.0`; // non-breaking
|
||||
|
||||
if (!semver.satisfies(actionVersion, compatibilityRange)) {
|
||||
throw new Error(
|
||||
`Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. ` +
|
||||
`Please update your workflow to use at least ${semver.minVersion(compatibilityRange)} version of the action.`
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user