feat: add file manager context actions (#3027)
* feat(app): add file manager context actions * fix: reshape file context actions --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
co-authored by
Mohamed Boudra
parent
a98d154b6b
commit
ca2b633286
@@ -0,0 +1,168 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
CheckoutDiscardChangesRequestSchema,
|
||||
CheckoutDiscardChangesResponseSchema,
|
||||
FileEntryCreateRequestSchema,
|
||||
FileEntryCreateResponseSchema,
|
||||
FileEntryDeleteRequestSchema,
|
||||
FileEntryDeleteResponseSchema,
|
||||
FileEntryDuplicateRequestSchema,
|
||||
FileEntryDuplicateResponseSchema,
|
||||
FileEntryRenameRequestSchema,
|
||||
FileEntryRenameResponseSchema,
|
||||
ServerInfoStatusPayloadSchema,
|
||||
} from "./messages.js";
|
||||
|
||||
describe("file context action messages", () => {
|
||||
test("keeps context-action capabilities optional and preserves advertised support", () => {
|
||||
const legacy = ServerInfoStatusPayloadSchema.parse({
|
||||
status: "server_info",
|
||||
serverId: "server-1",
|
||||
features: {},
|
||||
});
|
||||
expect(legacy.features?.fsEntryOps).toBeUndefined();
|
||||
expect(legacy.features?.fsEntryDuplicate).toBeUndefined();
|
||||
expect(legacy.features?.checkoutDiscardChanges).toBeUndefined();
|
||||
|
||||
const current = ServerInfoStatusPayloadSchema.parse({
|
||||
status: "server_info",
|
||||
serverId: "server-1",
|
||||
features: {
|
||||
fsEntryOps: true,
|
||||
fsEntryDuplicate: true,
|
||||
checkoutDiscardChanges: true,
|
||||
},
|
||||
});
|
||||
expect(current.features).toMatchObject({
|
||||
fsEntryOps: true,
|
||||
fsEntryDuplicate: true,
|
||||
checkoutDiscardChanges: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects discard requests without a path", () => {
|
||||
expect(() =>
|
||||
CheckoutDiscardChangesRequestSchema.parse({
|
||||
type: "checkout.discard_changes.request",
|
||||
cwd: "/workspace",
|
||||
paths: [],
|
||||
requestId: "discard-empty",
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("round-trips file entry create requests and responses", () => {
|
||||
const request = {
|
||||
type: "fs.entry.create.request",
|
||||
cwd: "/workspace",
|
||||
parentPath: "src/components",
|
||||
name: "button.tsx",
|
||||
kind: "file",
|
||||
requestId: "create-1",
|
||||
};
|
||||
expect(FileEntryCreateRequestSchema.parse(request)).toEqual(request);
|
||||
|
||||
const response = {
|
||||
type: "fs.entry.create.response",
|
||||
payload: {
|
||||
cwd: "/workspace",
|
||||
parentPath: "src/components",
|
||||
path: "src/components/button.tsx",
|
||||
success: true,
|
||||
error: null,
|
||||
requestId: "create-1",
|
||||
},
|
||||
};
|
||||
expect(FileEntryCreateResponseSchema.parse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
test("round-trips file entry rename requests and responses", () => {
|
||||
const request = {
|
||||
type: "fs.entry.rename.request",
|
||||
cwd: "/workspace",
|
||||
path: "src/old.ts",
|
||||
name: "new.ts",
|
||||
requestId: "rename-1",
|
||||
};
|
||||
expect(FileEntryRenameRequestSchema.parse(request)).toEqual(request);
|
||||
|
||||
const response = {
|
||||
type: "fs.entry.rename.response",
|
||||
payload: {
|
||||
cwd: "/workspace",
|
||||
path: "src/old.ts",
|
||||
renamedPath: "src/new.ts",
|
||||
success: true,
|
||||
error: null,
|
||||
requestId: "rename-1",
|
||||
},
|
||||
};
|
||||
expect(FileEntryRenameResponseSchema.parse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
test("round-trips file entry duplicate requests and responses", () => {
|
||||
const request = {
|
||||
type: "fs.entry.duplicate.request",
|
||||
cwd: "/workspace",
|
||||
path: "src/button.tsx",
|
||||
requestId: "duplicate-1",
|
||||
};
|
||||
expect(FileEntryDuplicateRequestSchema.parse(request)).toEqual(request);
|
||||
|
||||
const response = {
|
||||
type: "fs.entry.duplicate.response",
|
||||
payload: {
|
||||
cwd: "/workspace",
|
||||
path: "src/button.tsx",
|
||||
duplicatedPath: "src/button copy.tsx",
|
||||
success: true,
|
||||
error: null,
|
||||
requestId: "duplicate-1",
|
||||
},
|
||||
};
|
||||
expect(FileEntryDuplicateResponseSchema.parse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
test("round-trips file entry delete requests and responses", () => {
|
||||
const request = {
|
||||
type: "fs.entry.delete.request",
|
||||
cwd: "/workspace",
|
||||
path: "src/old.ts",
|
||||
requestId: "delete-1",
|
||||
};
|
||||
expect(FileEntryDeleteRequestSchema.parse(request)).toEqual(request);
|
||||
|
||||
const response = {
|
||||
type: "fs.entry.delete.response",
|
||||
payload: {
|
||||
cwd: "/workspace",
|
||||
path: "src/old.ts",
|
||||
success: false,
|
||||
error: "File or folder no longer exists",
|
||||
requestId: "delete-1",
|
||||
},
|
||||
};
|
||||
expect(FileEntryDeleteResponseSchema.parse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
test("round-trips checkout discard requests and responses", () => {
|
||||
const request = {
|
||||
type: "checkout.discard_changes.request",
|
||||
cwd: "/workspace",
|
||||
paths: ["src", "README.md"],
|
||||
requestId: "discard-1",
|
||||
};
|
||||
expect(CheckoutDiscardChangesRequestSchema.parse(request)).toEqual(request);
|
||||
|
||||
const response = {
|
||||
type: "checkout.discard_changes.response",
|
||||
payload: {
|
||||
cwd: "/workspace",
|
||||
success: false,
|
||||
error: { code: "UNKNOWN", message: "discard failed" },
|
||||
requestId: "discard-1",
|
||||
},
|
||||
};
|
||||
expect(CheckoutDiscardChangesResponseSchema.parse(response)).toEqual(response);
|
||||
});
|
||||
});
|
||||
@@ -1777,6 +1777,13 @@ export const CheckoutRefreshRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const CheckoutDiscardChangesRequestSchema = z.object({
|
||||
type: z.literal("checkout.discard_changes.request"),
|
||||
cwd: z.string(),
|
||||
paths: z.array(z.string()).min(1),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const CheckoutPrCreateRequestSchema = z.object({
|
||||
type: z.literal("checkout_pr_create_request"),
|
||||
cwd: z.string(),
|
||||
@@ -2204,6 +2211,8 @@ const DiffHunkSchema = z.object({
|
||||
|
||||
const ParsedDiffFileSchema = z.object({
|
||||
path: z.string(),
|
||||
// COMPAT(diffOldPath): added in v0.3.0, remove gate after 2027-02-09.
|
||||
oldPath: z.string().optional(),
|
||||
isNew: z.boolean(),
|
||||
isDeleted: z.boolean(),
|
||||
additions: z.number(),
|
||||
@@ -2291,6 +2300,37 @@ export const FileWriteRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const FileEntryCreateRequestSchema = z.object({
|
||||
type: z.literal("fs.entry.create.request"),
|
||||
cwd: z.string(),
|
||||
parentPath: z.string(),
|
||||
name: z.string(),
|
||||
kind: z.enum(["file", "directory"]),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const FileEntryRenameRequestSchema = z.object({
|
||||
type: z.literal("fs.entry.rename.request"),
|
||||
cwd: z.string(),
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const FileEntryDuplicateRequestSchema = z.object({
|
||||
type: z.literal("fs.entry.duplicate.request"),
|
||||
cwd: z.string(),
|
||||
path: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const FileEntryDeleteRequestSchema = z.object({
|
||||
type: z.literal("fs.entry.delete.request"),
|
||||
cwd: z.string(),
|
||||
path: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ProjectIconRequestSchema = z.object({
|
||||
type: z.literal("project_icon_request"),
|
||||
cwd: z.string(),
|
||||
@@ -2661,6 +2701,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
CheckoutPullRequestSchema,
|
||||
CheckoutPushRequestSchema,
|
||||
CheckoutRefreshRequestSchema,
|
||||
CheckoutDiscardChangesRequestSchema,
|
||||
CheckoutPrCreateRequestSchema,
|
||||
CheckoutPrMergeRequestSchema,
|
||||
CheckoutForgeSetAutoMergeRequestSchema,
|
||||
@@ -2699,6 +2740,10 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
FileSubscribeRequestSchema,
|
||||
FileUnsubscribeRequestSchema,
|
||||
FileWriteRequestSchema,
|
||||
FileEntryCreateRequestSchema,
|
||||
FileEntryRenameRequestSchema,
|
||||
FileEntryDuplicateRequestSchema,
|
||||
FileEntryDeleteRequestSchema,
|
||||
ProjectIconRequestSchema,
|
||||
ProjectIconGetRequestSchema,
|
||||
FileDownloadTokenRequestSchema,
|
||||
@@ -3004,6 +3049,12 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
workspaceScriptManagement: z.boolean().optional(),
|
||||
// COMPAT(projectCustomIcon): added in v0.2.0, remove after 2027-01-20.
|
||||
projectCustomIcon: z.boolean().optional(),
|
||||
// COMPAT(fsEntryOps): added in v0.3.0, remove gate after 2027-02-08.
|
||||
fsEntryOps: z.boolean().optional(),
|
||||
// COMPAT(fsEntryDuplicate): added in v0.3.0, remove gate after 2027-02-09.
|
||||
fsEntryDuplicate: z.boolean().optional(),
|
||||
// COMPAT(checkoutDiscardChanges): added in v0.3.0, remove gate after 2027-02-08.
|
||||
checkoutDiscardChanges: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
@@ -4493,6 +4544,16 @@ export const CheckoutGithubSetAutoMergeResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const CheckoutDiscardChangesResponseSchema = z.object({
|
||||
type: z.literal("checkout.discard_changes.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
success: z.boolean(),
|
||||
error: CheckoutErrorSchema.nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CheckoutCommitsListResponseSchema = z.object({
|
||||
type: z.literal("checkout.commits.list.response"),
|
||||
payload: z.object({
|
||||
@@ -4957,6 +5018,53 @@ export const FileWriteResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const FileEntryCreateResponseSchema = z.object({
|
||||
type: z.literal("fs.entry.create.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
parentPath: z.string(),
|
||||
path: z.string().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.string().nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const FileEntryRenameResponseSchema = z.object({
|
||||
type: z.literal("fs.entry.rename.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
path: z.string(),
|
||||
renamedPath: z.string().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.string().nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const FileEntryDuplicateResponseSchema = z.object({
|
||||
type: z.literal("fs.entry.duplicate.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
path: z.string(),
|
||||
duplicatedPath: z.string().nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.string().nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const FileEntryDeleteResponseSchema = z.object({
|
||||
type: z.literal("fs.entry.delete.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
path: z.string(),
|
||||
success: z.boolean(),
|
||||
error: z.string().nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const FileUpdateSchema = z.object({
|
||||
type: z.literal("fs.file.update"),
|
||||
payload: z.object({
|
||||
@@ -5549,6 +5657,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
CheckoutPullResponseSchema,
|
||||
CheckoutPushResponseSchema,
|
||||
CheckoutRefreshResponseSchema,
|
||||
CheckoutDiscardChangesResponseSchema,
|
||||
CheckoutPrCreateResponseSchema,
|
||||
CheckoutPrMergeResponseSchema,
|
||||
CheckoutForgeSetAutoMergeResponseSchema,
|
||||
@@ -5576,6 +5685,10 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
FileSubscribeResponseSchema,
|
||||
FileUnsubscribeResponseSchema,
|
||||
FileWriteResponseSchema,
|
||||
FileEntryCreateResponseSchema,
|
||||
FileEntryRenameResponseSchema,
|
||||
FileEntryDuplicateResponseSchema,
|
||||
FileEntryDeleteResponseSchema,
|
||||
FileUpdateSchema,
|
||||
ProjectIconResponseSchema,
|
||||
ProjectIconGetResponseSchema,
|
||||
@@ -5896,6 +6009,8 @@ export type CheckoutPushRequest = z.infer<typeof CheckoutPushRequestSchema>;
|
||||
export type CheckoutPushResponse = z.infer<typeof CheckoutPushResponseSchema>;
|
||||
export type CheckoutRefreshRequest = z.infer<typeof CheckoutRefreshRequestSchema>;
|
||||
export type CheckoutRefreshResponse = z.infer<typeof CheckoutRefreshResponseSchema>;
|
||||
export type CheckoutDiscardChangesRequest = z.infer<typeof CheckoutDiscardChangesRequestSchema>;
|
||||
export type CheckoutDiscardChangesResponse = z.infer<typeof CheckoutDiscardChangesResponseSchema>;
|
||||
export type CheckoutCommitFile = z.infer<typeof CheckoutCommitFileSchema>;
|
||||
export type CheckoutCommit = z.infer<typeof CheckoutCommitSchema>;
|
||||
export type CheckoutCommitsListRequest = z.infer<typeof CheckoutCommitsListRequestSchema>;
|
||||
@@ -5999,6 +6114,14 @@ export type FileUnsubscribeRequest = z.infer<typeof FileUnsubscribeRequestSchema
|
||||
export type FileUnsubscribeResponse = z.infer<typeof FileUnsubscribeResponseSchema>;
|
||||
export type FileWriteRequest = z.infer<typeof FileWriteRequestSchema>;
|
||||
export type FileWriteResponse = z.infer<typeof FileWriteResponseSchema>;
|
||||
export type FileEntryCreateRequest = z.infer<typeof FileEntryCreateRequestSchema>;
|
||||
export type FileEntryCreateResponse = z.infer<typeof FileEntryCreateResponseSchema>;
|
||||
export type FileEntryRenameRequest = z.infer<typeof FileEntryRenameRequestSchema>;
|
||||
export type FileEntryRenameResponse = z.infer<typeof FileEntryRenameResponseSchema>;
|
||||
export type FileEntryDuplicateRequest = z.infer<typeof FileEntryDuplicateRequestSchema>;
|
||||
export type FileEntryDuplicateResponse = z.infer<typeof FileEntryDuplicateResponseSchema>;
|
||||
export type FileEntryDeleteRequest = z.infer<typeof FileEntryDeleteRequestSchema>;
|
||||
export type FileEntryDeleteResponse = z.infer<typeof FileEntryDeleteResponseSchema>;
|
||||
export type FileWriteResult = z.infer<typeof FileWriteResultSchema>;
|
||||
export type FileUpdate = z.infer<typeof FileUpdateSchema>;
|
||||
export type ProjectIconRequest = z.infer<typeof ProjectIconRequestSchema>;
|
||||
|
||||
Reference in New Issue
Block a user