mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
fix: make trusted publisher environment optional (#1489)
* fix: make trusted publisher environment optional * fix: avoid env mismatch on unpinned trusted publishes --------- Co-authored-by: Onur <onur@solmaz.io>
This commit is contained in:
@@ -3582,6 +3582,82 @@ describe("httpApiV1 handlers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("mints a short-lived publish token without environment when none is pinned", async () => {
|
||||
vi.mocked(verifyGitHubActionsTrustedPublishJwt).mockResolvedValue({
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
environment: "clawhub-release",
|
||||
runId: "101",
|
||||
runAttempt: "1",
|
||||
sha: "abc123",
|
||||
ref: "refs/heads/main",
|
||||
refType: "branch",
|
||||
actor: "onur",
|
||||
actorId: "42",
|
||||
} as never);
|
||||
const runMutation = vi.fn(async (_mutation: unknown, args: Record<string, unknown>) => {
|
||||
if ("key" in args) return okRate();
|
||||
return "mutation:ok";
|
||||
});
|
||||
const runQuery = vi.fn(async (_query: unknown, args: Record<string, unknown>) => {
|
||||
if ("name" in args) {
|
||||
return {
|
||||
_id: "packages:1",
|
||||
name: "@openclaw/demo-plugin",
|
||||
ownerUserId: "users:owner",
|
||||
};
|
||||
}
|
||||
if ("packageId" in args) {
|
||||
return {
|
||||
_id: "packageTrustedPublishers:1",
|
||||
packageId: "packages:1",
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const response = await __handlers.mintPublishTokenV1Handler(
|
||||
makeCtx({ runQuery, runMutation }),
|
||||
new Request("https://example.com/api/v1/publish/token/mint", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
packageName: "@openclaw/demo-plugin",
|
||||
version: "1.0.0",
|
||||
githubOidcToken: "gh.jwt",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.status !== 200) throw new Error(await response.text());
|
||||
const body = await response.json();
|
||||
expect(body.token).toEqual(expect.any(String));
|
||||
expect(body.expiresAt).toEqual(expect.any(Number));
|
||||
const createCall = runMutation.mock.calls.find(
|
||||
([, args]) => typeof args === "object" && args !== null && "packageId" in args && "tokenHash" in args,
|
||||
);
|
||||
expect(createCall?.[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
packageId: "packages:1",
|
||||
version: "1.0.0",
|
||||
repository: "openclaw/openclaw",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
runId: "101",
|
||||
sha: "abc123",
|
||||
}),
|
||||
);
|
||||
expect(createCall?.[1]).not.toHaveProperty("environment");
|
||||
});
|
||||
|
||||
it("sets trusted publisher config for a package", async () => {
|
||||
vi.mocked(requireApiTokenUser).mockResolvedValue({
|
||||
userId: "users:1",
|
||||
@@ -3641,6 +3717,75 @@ describe("httpApiV1 handlers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sets trusted publisher config for a package without environment", async () => {
|
||||
vi.mocked(requireApiTokenUser).mockResolvedValue({
|
||||
userId: "users:1",
|
||||
user: { _id: "users:1", handle: "p" },
|
||||
} as never);
|
||||
vi.mocked(fetchGitHubRepositoryIdentity).mockResolvedValue({
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
} as never);
|
||||
const runMutation = vi.fn(async (_mutation: unknown, args: Record<string, unknown>) => {
|
||||
if ("key" in args) return okRate();
|
||||
return {
|
||||
_id: "packageTrustedPublishers:1",
|
||||
packageId: "packages:1",
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
};
|
||||
});
|
||||
|
||||
const response = await __handlers.packagesPostRouterV1Handler(
|
||||
makeCtx({ runMutation }),
|
||||
new Request(
|
||||
"https://example.com/api/v1/packages/%40openclaw%2Fdemo-plugin/trusted-publisher",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: "Bearer clh_test",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
repository: "https://github.com/openclaw/openclaw",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.status !== 200) throw new Error(await response.text());
|
||||
expect(fetchGitHubRepositoryIdentity).toHaveBeenCalledWith("https://github.com/openclaw/openclaw");
|
||||
expect(await response.json()).toEqual({
|
||||
trustedPublisher: {
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
},
|
||||
});
|
||||
const setCall = runMutation.mock.calls.find(
|
||||
([, args]) => typeof args === "object" && args !== null && "packageName" in args && "actorUserId" in args,
|
||||
);
|
||||
expect(setCall?.[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
actorUserId: "users:1",
|
||||
packageName: "@openclaw/demo-plugin",
|
||||
repository: "openclaw/openclaw",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
}),
|
||||
);
|
||||
expect(setCall?.[1]).not.toHaveProperty("environment");
|
||||
});
|
||||
|
||||
it("deletes trusted publisher config for a package", async () => {
|
||||
vi.mocked(requireApiTokenUser).mockResolvedValue({
|
||||
userId: "users:1",
|
||||
|
||||
@@ -167,7 +167,7 @@ type PackageTrustedPublisherLike = {
|
||||
repositoryOwner: string;
|
||||
repositoryOwnerId: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
};
|
||||
@@ -186,7 +186,7 @@ function toPublicTrustedPublisher(trustedPublisher: PackageTrustedPublisherLike
|
||||
repositoryOwner: trustedPublisher.repositoryOwner,
|
||||
repositoryOwnerId: trustedPublisher.repositoryOwnerId,
|
||||
workflowFilename: trustedPublisher.workflowFilename,
|
||||
environment: trustedPublisher.environment,
|
||||
...(trustedPublisher.environment ? { environment: trustedPublisher.environment } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@ export async function mintPublishTokenV1Handler(ctx: ActionCtx, request: Request
|
||||
repositoryOwner: trustedPublisher.repositoryOwner,
|
||||
repositoryOwnerId: trustedPublisher.repositoryOwnerId,
|
||||
workflowFilename: trustedPublisher.workflowFilename,
|
||||
environment: trustedPublisher.environment,
|
||||
...(trustedPublisher.environment ? { environment: trustedPublisher.environment } : {}),
|
||||
});
|
||||
const { token, prefix } = generateToken();
|
||||
const tokenHash = await hashToken(token);
|
||||
@@ -730,7 +730,7 @@ export async function mintPublishTokenV1Handler(ctx: ActionCtx, request: Request
|
||||
repositoryOwner: verified.repositoryOwner,
|
||||
repositoryOwnerId: verified.repositoryOwnerId,
|
||||
workflowFilename: verified.workflowFilename,
|
||||
environment: verified.environment,
|
||||
...(trustedPublisher.environment ? { environment: trustedPublisher.environment } : {}),
|
||||
runId: verified.runId,
|
||||
runAttempt: verified.runAttempt,
|
||||
sha: verified.sha,
|
||||
@@ -749,7 +749,7 @@ export async function mintPublishTokenV1Handler(ctx: ActionCtx, request: Request
|
||||
version: payload.version,
|
||||
repository: verified.repository,
|
||||
workflowFilename: verified.workflowFilename,
|
||||
environment: verified.environment,
|
||||
...(verified.environment ? { environment: verified.environment } : {}),
|
||||
runId: verified.runId,
|
||||
runAttempt: verified.runAttempt,
|
||||
sha: verified.sha,
|
||||
@@ -768,7 +768,7 @@ export async function mintPublishTokenV1Handler(ctx: ActionCtx, request: Request
|
||||
version: payload.version,
|
||||
repository: trustedPublisher.repository,
|
||||
workflowFilename: trustedPublisher.workflowFilename,
|
||||
environment: trustedPublisher.environment,
|
||||
...(trustedPublisher.environment ? { environment: trustedPublisher.environment } : {}),
|
||||
decision: "rejected",
|
||||
reason: error instanceof Error ? error.message : "Token verification failed",
|
||||
},
|
||||
@@ -798,7 +798,7 @@ export async function packagesPostRouterV1Handler(ctx: ActionCtx, request: Reque
|
||||
) as {
|
||||
repository: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string;
|
||||
};
|
||||
const repositoryIdentity = await fetchGitHubRepositoryIdentity(body.repository);
|
||||
const trustedPublisher = await runMutationRef<PackageTrustedPublisherLike | null>(
|
||||
@@ -812,7 +812,7 @@ export async function packagesPostRouterV1Handler(ctx: ActionCtx, request: Reque
|
||||
repositoryOwner: repositoryIdentity.repositoryOwner,
|
||||
repositoryOwnerId: repositoryIdentity.repositoryOwnerId,
|
||||
workflowFilename: body.workflowFilename,
|
||||
environment: body.environment,
|
||||
...(body.environment ? { environment: body.environment } : {}),
|
||||
},
|
||||
);
|
||||
return json({ trustedPublisher: toPublicTrustedPublisher(trustedPublisher) }, 200, rate.headers);
|
||||
|
||||
@@ -15,6 +15,10 @@ const trustedPublisher: TrustedGitHubActionsPublisher = {
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
environment: "clawhub-plugin-release",
|
||||
};
|
||||
const trustedPublisherWithoutEnvironment: TrustedGitHubActionsPublisher = {
|
||||
...trustedPublisher,
|
||||
environment: undefined,
|
||||
};
|
||||
const signingKeyPairPromise = crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSASSA-PKCS1-v1_5",
|
||||
@@ -84,6 +88,51 @@ describe("verifyGitHubActionsTrustedPublishJwt", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts a valid GitHub Actions token when no environment is pinned", async () => {
|
||||
const { token, jwks } = await createSignedToken({
|
||||
repository: trustedPublisher.repository,
|
||||
repository_id: trustedPublisher.repositoryId,
|
||||
repository_owner: trustedPublisher.repositoryOwner,
|
||||
repository_owner_id: trustedPublisher.repositoryOwnerId,
|
||||
workflow_ref:
|
||||
"openclaw/openclaw/.github/workflows/plugin-clawhub-release.yml@refs/heads/main",
|
||||
runner_environment: "github-hosted",
|
||||
event_name: "workflow_dispatch",
|
||||
workflow: "Plugin ClawHub Release",
|
||||
sha: "deadbeef",
|
||||
ref: "refs/heads/main",
|
||||
ref_type: "branch",
|
||||
actor: "onur",
|
||||
actor_id: "42",
|
||||
run_id: "100",
|
||||
run_attempt: "2",
|
||||
iss: "https://token.actions.githubusercontent.com",
|
||||
aud: "clawhub",
|
||||
exp: Math.floor(Date.now() / 1000) + 300,
|
||||
iat: Math.floor(Date.now() / 1000) - 5,
|
||||
});
|
||||
|
||||
const identity = await verifyGitHubActionsTrustedPublishJwt(token, trustedPublisherWithoutEnvironment, {
|
||||
fetchImpl: async () =>
|
||||
new Response(JSON.stringify({ keys: [jwks] }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(identity).toMatchObject({
|
||||
repository: trustedPublisher.repository,
|
||||
repositoryId: trustedPublisher.repositoryId,
|
||||
repositoryOwner: trustedPublisher.repositoryOwner,
|
||||
repositoryOwnerId: trustedPublisher.repositoryOwnerId,
|
||||
workflowFilename: trustedPublisher.workflowFilename,
|
||||
runId: "100",
|
||||
runAttempt: "2",
|
||||
sha: "deadbeef",
|
||||
});
|
||||
expect(identity.environment).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects reusable workflow tokens", async () => {
|
||||
const { token, jwks } = await createSignedToken({
|
||||
repository: trustedPublisher.repository,
|
||||
|
||||
@@ -16,7 +16,7 @@ export type TrustedGitHubActionsPublisher = {
|
||||
repositoryOwner: string;
|
||||
repositoryOwnerId: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string;
|
||||
};
|
||||
|
||||
export type VerifiedGitHubActionsIdentity = {
|
||||
@@ -28,7 +28,7 @@ export type VerifiedGitHubActionsIdentity = {
|
||||
workflowName: string;
|
||||
workflowRef: string;
|
||||
jobWorkflowRef?: string;
|
||||
environment: string;
|
||||
environment?: string;
|
||||
runnerEnvironment: string;
|
||||
eventName: string;
|
||||
sha: string;
|
||||
@@ -123,7 +123,7 @@ export async function verifyGitHubActionsTrustedPublishJwt(
|
||||
const workflow = parseWorkflowRef(workflowRef, repository);
|
||||
const jobWorkflowRef = optionalString(payload.job_workflow_ref);
|
||||
const runnerEnvironment = requireString(payload.runner_environment, "runner_environment");
|
||||
const environment = requireString(payload.environment, "environment");
|
||||
const environment = optionalString(payload.environment);
|
||||
const eventName = requireString(payload.event_name, "event_name");
|
||||
const workflowName = requireString(payload.workflow, "workflow");
|
||||
const sha = requireString(payload.sha, "sha");
|
||||
@@ -171,14 +171,14 @@ export async function verifyGitHubActionsTrustedPublishJwt(
|
||||
if (runnerEnvironment !== "github-hosted") {
|
||||
throw new Error(`Only GitHub-hosted runners may mint trusted publish tokens, got ${runnerEnvironment}`);
|
||||
}
|
||||
// v1 keeps secretless publishing behind a manual, environment-protected entry
|
||||
// point. Tag and release automation should keep using the token path for now.
|
||||
// v1 keeps secretless publishing behind a manual entry point. Environment
|
||||
// pinning is optional, but if configured it must match exactly.
|
||||
if (eventName !== "workflow_dispatch") {
|
||||
throw new Error(`Trusted publishing requires workflow_dispatch, got ${eventName}`);
|
||||
}
|
||||
if (environment !== trustedPublisher.environment) {
|
||||
if (trustedPublisher.environment && environment !== trustedPublisher.environment) {
|
||||
throw new Error(
|
||||
`GitHub OIDC environment mismatch: expected ${trustedPublisher.environment}, got ${environment}`,
|
||||
`GitHub OIDC environment mismatch: expected ${trustedPublisher.environment}, got ${formatClaimValue(environment ?? "<missing>")}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ export async function verifyGitHubActionsTrustedPublishJwt(
|
||||
workflowName,
|
||||
workflowRef,
|
||||
...(jobWorkflowRef ? { jobWorkflowRef } : {}),
|
||||
environment,
|
||||
...(environment ? { environment } : {}),
|
||||
runnerEnvironment,
|
||||
eventName,
|
||||
sha,
|
||||
|
||||
@@ -13,7 +13,7 @@ export const createInternal = internalMutation({
|
||||
repositoryOwner: v.string(),
|
||||
repositoryOwnerId: v.string(),
|
||||
workflowFilename: v.string(),
|
||||
environment: v.string(),
|
||||
environment: v.optional(v.string()),
|
||||
runId: v.string(),
|
||||
runAttempt: v.string(),
|
||||
sha: v.string(),
|
||||
|
||||
@@ -2161,6 +2161,84 @@ describe("packages public queries", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts trusted publish tokens when no environment is pinned", async () => {
|
||||
const runMutation = vi.fn(async (_ref: unknown, args: unknown) => {
|
||||
if (
|
||||
typeof args === "object" &&
|
||||
args !== null &&
|
||||
"name" in args &&
|
||||
"version" in args &&
|
||||
"files" in args
|
||||
) {
|
||||
return {
|
||||
ok: true,
|
||||
packageId: "packages:demo",
|
||||
releaseId: "packageReleases:demo-2",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const trustedPublisher = {
|
||||
_id: "packageTrustedPublishers:1",
|
||||
packageId: "packages:demo",
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
};
|
||||
const ctx = {
|
||||
runQuery: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
_id: "packagePublishTokens:1",
|
||||
packageId: "packages:demo",
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
version: "1.0.0",
|
||||
sha: "abc123",
|
||||
ref: "refs/heads/main",
|
||||
runId: "100",
|
||||
runAttempt: "1",
|
||||
expiresAt: Date.now() + 60_000,
|
||||
})
|
||||
.mockResolvedValueOnce(trustedPublisher)
|
||||
.mockResolvedValueOnce(makePackageDoc({ family: "bundle-plugin" }))
|
||||
.mockResolvedValueOnce(trustedPublisher)
|
||||
.mockResolvedValueOnce(null),
|
||||
runMutation,
|
||||
scheduler: {
|
||||
runAfter: vi.fn(),
|
||||
},
|
||||
storage: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
publishPackageForTrustedPublisherInternalHandler(ctx as never, {
|
||||
publishTokenId: "packagePublishTokens:1",
|
||||
payload: {
|
||||
name: "demo-plugin",
|
||||
family: "bundle-plugin",
|
||||
version: "1.0.0",
|
||||
changelog: "init",
|
||||
bundle: { hostTargets: ["desktop"] },
|
||||
files: [],
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
ok: true,
|
||||
packageId: "packages:demo",
|
||||
releaseId: "packageReleases:demo-2",
|
||||
});
|
||||
});
|
||||
|
||||
it("requires manual override for user-auth publishes when trusted publisher config exists", async () => {
|
||||
const runMutation = vi.fn(async (_ref: unknown, args: unknown) => {
|
||||
if (
|
||||
|
||||
+4
-5
@@ -1306,7 +1306,7 @@ export const setTrustedPublisherForUserInternal = internalMutation({
|
||||
repositoryOwner: v.string(),
|
||||
repositoryOwnerId: v.string(),
|
||||
workflowFilename: v.string(),
|
||||
environment: v.string(),
|
||||
environment: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const pkg = await getPackageByNormalizedName(ctx, normalizePackageName(args.packageName));
|
||||
@@ -1317,8 +1317,7 @@ export const setTrustedPublisherForUserInternal = internalMutation({
|
||||
await requireTrustedPublisherEditor(ctx, pkg, args.actorUserId);
|
||||
|
||||
const workflowFilename = normalizeWorkflowFilenameOrThrow(args.workflowFilename);
|
||||
const environment = args.environment.trim();
|
||||
if (!environment) throw new ConvexError("Environment is required");
|
||||
const environment = args.environment?.trim() || undefined;
|
||||
|
||||
const existing = await getPackageTrustedPublisherByPackageId(ctx, pkg._id);
|
||||
const now = Date.now();
|
||||
@@ -1356,7 +1355,7 @@ export const setTrustedPublisherForUserInternal = internalMutation({
|
||||
repositoryOwner: args.repositoryOwner,
|
||||
repositoryOwnerId: args.repositoryOwnerId,
|
||||
workflowFilename,
|
||||
environment,
|
||||
...(environment ? { environment } : {}),
|
||||
},
|
||||
createdAt: now,
|
||||
});
|
||||
@@ -1635,7 +1634,7 @@ function doesTrustedPublisherMatchPublishToken(
|
||||
publishToken: Doc<"packagePublishTokens">,
|
||||
) {
|
||||
return Boolean(
|
||||
trustedPublisher &&
|
||||
trustedPublisher &&
|
||||
trustedPublisher.packageId === publishToken.packageId &&
|
||||
trustedPublisher.provider === publishToken.provider &&
|
||||
trustedPublisher.repository === publishToken.repository &&
|
||||
|
||||
+2
-2
@@ -753,7 +753,7 @@ const packageTrustedPublishers = defineTable({
|
||||
repositoryOwner: v.string(),
|
||||
repositoryOwnerId: v.string(),
|
||||
workflowFilename: v.string(),
|
||||
environment: v.string(),
|
||||
environment: v.optional(v.string()),
|
||||
createdByUserId: v.id("users"),
|
||||
updatedByUserId: v.id("users"),
|
||||
createdAt: v.number(),
|
||||
@@ -773,7 +773,7 @@ const packagePublishTokens = defineTable({
|
||||
repositoryOwner: v.string(),
|
||||
repositoryOwnerId: v.string(),
|
||||
workflowFilename: v.string(),
|
||||
environment: v.string(),
|
||||
environment: v.optional(v.string()),
|
||||
runId: v.string(),
|
||||
runAttempt: v.string(),
|
||||
sha: v.string(),
|
||||
|
||||
@@ -436,7 +436,7 @@ trustedPublisherCmd
|
||||
.argument("<name>", "Package name")
|
||||
.requiredOption("--repository <repo>", "GitHub repo (owner/repo or URL)")
|
||||
.requiredOption("--workflow-filename <file>", "Workflow filename, for example publish.yml")
|
||||
.requiredOption("--environment <name>", "Protected GitHub environment name")
|
||||
.option("--environment <name>", "Optional GitHub environment name to pin")
|
||||
.option("--json", "Output JSON")
|
||||
.action(async (name, options) => {
|
||||
const opts = await resolveGlobalOpts();
|
||||
|
||||
@@ -1054,6 +1054,26 @@ describe("package commands", () => {
|
||||
expect(mockLog).toHaveBeenCalledWith("Environment: clawhub-release");
|
||||
});
|
||||
|
||||
it("gets trusted publisher config without a pinned environment", async () => {
|
||||
httpMocks.apiRequest.mockResolvedValueOnce({
|
||||
trustedPublisher: {
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
},
|
||||
});
|
||||
|
||||
await cmdGetPackageTrustedPublisher(makeOpts(), "@openclaw/zalo");
|
||||
|
||||
expect(mockLog).toHaveBeenCalledWith("Provider: github-actions");
|
||||
expect(mockLog).toHaveBeenCalledWith("Repository: openclaw/openclaw");
|
||||
expect(mockLog).toHaveBeenCalledWith("Workflow: plugin-clawhub-release.yml");
|
||||
expect(mockLog).not.toHaveBeenCalledWith(expect.stringContaining("Environment:"));
|
||||
});
|
||||
|
||||
it("sets trusted publisher config for a package", async () => {
|
||||
httpMocks.apiRequest.mockResolvedValueOnce({
|
||||
trustedPublisher: {
|
||||
@@ -1090,6 +1110,39 @@ describe("package commands", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sets trusted publisher config for a package without environment", async () => {
|
||||
httpMocks.apiRequest.mockResolvedValueOnce({
|
||||
trustedPublisher: {
|
||||
provider: "github-actions",
|
||||
repository: "openclaw/openclaw",
|
||||
repositoryId: "1",
|
||||
repositoryOwner: "openclaw",
|
||||
repositoryOwnerId: "2",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
},
|
||||
});
|
||||
|
||||
await cmdSetPackageTrustedPublisher(makeOpts(), "@openclaw/zalo", {
|
||||
repository: "openclaw/openclaw",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
});
|
||||
|
||||
expect(authTokenMocks.requireAuthToken).toHaveBeenCalled();
|
||||
expect(httpMocks.apiRequest).toHaveBeenCalledWith(
|
||||
"https://clawhub.ai",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
path: "/api/v1/packages/%40openclaw%2Fzalo/trusted-publisher",
|
||||
token: "tkn",
|
||||
body: {
|
||||
repository: "openclaw/openclaw",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
},
|
||||
}),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("deletes trusted publisher config for a package", async () => {
|
||||
httpMocks.apiRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ type PackageTrustedPublisherGetOptions = {
|
||||
type PackageTrustedPublisherSetOptions = {
|
||||
repository: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string;
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
@@ -387,10 +387,9 @@ export async function cmdSetPackageTrustedPublisher(
|
||||
const trimmed = normalizePackageNameOrFail(packageName);
|
||||
const repository = options.repository?.trim();
|
||||
const workflowFilename = options.workflowFilename?.trim();
|
||||
const environment = options.environment?.trim();
|
||||
const environment = options.environment?.trim() || undefined;
|
||||
if (!repository) fail("--repository required");
|
||||
if (!workflowFilename) fail("--workflow-filename required");
|
||||
if (!environment) fail("--environment required");
|
||||
|
||||
const token = await requireAuthToken();
|
||||
const registry = await getRegistry(opts, { cache: true });
|
||||
@@ -402,7 +401,11 @@ export async function cmdSetPackageTrustedPublisher(
|
||||
method: "POST",
|
||||
path: `${ApiRoutes.packages}/${encodeURIComponent(trimmed)}/trusted-publisher`,
|
||||
token,
|
||||
body: { repository, workflowFilename, environment },
|
||||
body: {
|
||||
repository,
|
||||
workflowFilename,
|
||||
...(environment ? { environment } : {}),
|
||||
},
|
||||
},
|
||||
ApiV1PackageTrustedPublisherResponseSchema,
|
||||
);
|
||||
@@ -643,7 +646,9 @@ function printTrustedPublisher(trustedPublisher: PackageTrustedPublisher) {
|
||||
console.log(`Provider: ${trustedPublisher.provider}`);
|
||||
console.log(`Repository: ${trustedPublisher.repository}`);
|
||||
console.log(`Workflow: ${trustedPublisher.workflowFilename}`);
|
||||
console.log(`Environment: ${trustedPublisher.environment}`);
|
||||
if (trustedPublisher.environment) {
|
||||
console.log(`Environment: ${trustedPublisher.environment}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printCompatibility(compatibility: PackageCompatibility | null | undefined) {
|
||||
|
||||
@@ -122,7 +122,7 @@ export const PackageTrustedPublisherSchema = type({
|
||||
repositoryOwner: "string",
|
||||
repositoryOwnerId: "string",
|
||||
workflowFilename: "string",
|
||||
environment: "string",
|
||||
environment: "string?",
|
||||
});
|
||||
export type PackageTrustedPublisher = (typeof PackageTrustedPublisherSchema)[inferred];
|
||||
|
||||
@@ -239,7 +239,7 @@ export type ApiV1PackagePublishResponse = (typeof ApiV1PackagePublishResponseSch
|
||||
export const PackageTrustedPublisherUpsertRequestSchema = type({
|
||||
repository: "string",
|
||||
workflowFilename: "string",
|
||||
environment: "string",
|
||||
environment: "string?",
|
||||
});
|
||||
export type PackageTrustedPublisherUpsertRequest =
|
||||
(typeof PackageTrustedPublisherUpsertRequestSchema)[inferred];
|
||||
|
||||
Vendored
+3
-3
@@ -116,7 +116,7 @@ export declare const PackageTrustedPublisherSchema: import("arktype/internal/var
|
||||
repositoryOwner: string;
|
||||
repositoryOwnerId: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string | undefined;
|
||||
}, {}>;
|
||||
export type PackageTrustedPublisher = (typeof PackageTrustedPublisherSchema)[inferred];
|
||||
export declare const PackagePublishRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
||||
@@ -378,7 +378,7 @@ export type ApiV1PackagePublishResponse = (typeof ApiV1PackagePublishResponseSch
|
||||
export declare const PackageTrustedPublisherUpsertRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
||||
repository: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string | undefined;
|
||||
}, {}>;
|
||||
export type PackageTrustedPublisherUpsertRequest = (typeof PackageTrustedPublisherUpsertRequestSchema)[inferred];
|
||||
export declare const ApiV1PackageTrustedPublisherResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
||||
@@ -389,7 +389,7 @@ export declare const ApiV1PackageTrustedPublisherResponseSchema: import("arktype
|
||||
repositoryOwner: string;
|
||||
repositoryOwnerId: string;
|
||||
workflowFilename: string;
|
||||
environment: string;
|
||||
environment?: string | undefined;
|
||||
} | null;
|
||||
}, {}>;
|
||||
export type ApiV1PackageTrustedPublisherResponse = (typeof ApiV1PackageTrustedPublisherResponseSchema)[inferred];
|
||||
|
||||
Vendored
+2
-2
@@ -92,7 +92,7 @@ export const PackageTrustedPublisherSchema = type({
|
||||
repositoryOwner: "string",
|
||||
repositoryOwnerId: "string",
|
||||
workflowFilename: "string",
|
||||
environment: "string",
|
||||
environment: "string?",
|
||||
});
|
||||
export const PackagePublishRequestSchema = type({
|
||||
name: "string",
|
||||
@@ -196,7 +196,7 @@ export const ApiV1PackagePublishResponseSchema = type({
|
||||
export const PackageTrustedPublisherUpsertRequestSchema = type({
|
||||
repository: "string",
|
||||
workflowFilename: "string",
|
||||
environment: "string",
|
||||
environment: "string?",
|
||||
});
|
||||
export const ApiV1PackageTrustedPublisherResponseSchema = type({
|
||||
trustedPublisher: PackageTrustedPublisherSchema.or("null"),
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -122,7 +122,7 @@ export const PackageTrustedPublisherSchema = type({
|
||||
repositoryOwner: "string",
|
||||
repositoryOwnerId: "string",
|
||||
workflowFilename: "string",
|
||||
environment: "string",
|
||||
environment: "string?",
|
||||
});
|
||||
export type PackageTrustedPublisher = (typeof PackageTrustedPublisherSchema)[inferred];
|
||||
|
||||
@@ -245,7 +245,7 @@ export type ApiV1PackagePublishResponse = (typeof ApiV1PackagePublishResponseSch
|
||||
export const PackageTrustedPublisherUpsertRequestSchema = type({
|
||||
repository: "string",
|
||||
workflowFilename: "string",
|
||||
environment: "string",
|
||||
environment: "string?",
|
||||
});
|
||||
export type PackageTrustedPublisherUpsertRequest =
|
||||
(typeof PackageTrustedPublisherUpsertRequestSchema)[inferred];
|
||||
|
||||
Reference in New Issue
Block a user