Merge main into proxy branch (formatting and docs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-03-13 12:09:59 -07:00
parent 3c4035097a
commit 7314aedc71
712 changed files with 13408 additions and 13322 deletions
-1
View File
@@ -37,7 +37,6 @@ The plugin analyzes your gRPC request configuration and generates a properly for
### Simple Unary Call
```bash
grpcurl -plaintext \
-d '{"name": "John Doe"}' \
+3 -3
View File
@@ -1,17 +1,17 @@
{
"name": "@yaak/action-copy-grpcurl",
"displayName": "Copy as gRPCurl",
"version": "0.1.0",
"private": true,
"description": "Copy gRPC request as a grpcurl command",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/action-copy-grpcurl"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vitest --run tests"
"test": "vp test --run tests"
}
}
+39 -39
View File
@@ -1,24 +1,24 @@
import path from 'node:path';
import type { GrpcRequest, PluginDefinition } from '@yaakapp/api';
import path from "node:path";
import type { GrpcRequest, PluginDefinition } from "@yaakapp/api";
const NEWLINE = '\\\n ';
const NEWLINE = "\\\n ";
export const plugin: PluginDefinition = {
grpcRequestActions: [
{
label: 'Copy as gRPCurl',
icon: 'copy',
label: "Copy as gRPCurl",
icon: "copy",
async onSelect(ctx, args) {
const rendered_request = await ctx.grpcRequest.render({
grpcRequest: args.grpcRequest,
purpose: 'send',
purpose: "send",
});
const data = await convert(rendered_request, args.protoFiles);
await ctx.clipboard.copyText(data);
await ctx.toast.show({
message: 'Command copied to clipboard',
icon: 'copy',
color: 'success',
message: "Command copied to clipboard",
icon: "copy",
color: "success",
});
},
},
@@ -26,14 +26,14 @@ export const plugin: PluginDefinition = {
};
export async function convert(request: Partial<GrpcRequest>, allProtoFiles: string[]) {
const xs = ['grpcurl'];
const xs = ["grpcurl"];
if (request.url?.startsWith('http://')) {
xs.push('-plaintext');
if (request.url?.startsWith("http://")) {
xs.push("-plaintext");
}
const protoIncludes = allProtoFiles.filter((f) => !f.endsWith('.proto'));
const protoFiles = allProtoFiles.filter((f) => f.endsWith('.proto'));
const protoIncludes = allProtoFiles.filter((f) => !f.endsWith(".proto"));
const protoFiles = allProtoFiles.filter((f) => f.endsWith(".proto"));
const inferredIncludes = new Set<string>();
for (const f of protoFiles) {
@@ -41,59 +41,59 @@ export async function convert(request: Partial<GrpcRequest>, allProtoFiles: stri
if (protoDir) {
inferredIncludes.add(protoDir);
} else {
inferredIncludes.add(path.posix.join(f, '..'));
inferredIncludes.add(path.posix.join(f, '..', '..'));
inferredIncludes.add(path.posix.join(f, ".."));
inferredIncludes.add(path.posix.join(f, "..", ".."));
}
}
for (const f of protoIncludes) {
xs.push('-import-path', quote(f));
xs.push("-import-path", quote(f));
xs.push(NEWLINE);
}
for (const f of inferredIncludes.values()) {
xs.push('-import-path', quote(f));
xs.push("-import-path", quote(f));
xs.push(NEWLINE);
}
for (const f of protoFiles) {
xs.push('-proto', quote(f));
xs.push("-proto", quote(f));
xs.push(NEWLINE);
}
// Add headers
for (const h of (request.metadata ?? []).filter(onlyEnabled)) {
xs.push('-H', quote(`${h.name}: ${h.value}`));
xs.push("-H", quote(`${h.name}: ${h.value}`));
xs.push(NEWLINE);
}
// Add basic authentication
if (request.authentication?.disabled !== true) {
if (request.authenticationType === 'basic') {
const user = request.authentication?.username ?? '';
const pass = request.authentication?.password ?? '';
if (request.authenticationType === "basic") {
const user = request.authentication?.username ?? "";
const pass = request.authentication?.password ?? "";
const encoded = btoa(`${user}:${pass}`);
xs.push('-H', quote(`Authorization: Basic ${encoded}`));
xs.push("-H", quote(`Authorization: Basic ${encoded}`));
xs.push(NEWLINE);
} else if (request.authenticationType === 'bearer') {
} else if (request.authenticationType === "bearer") {
// Add bearer authentication
xs.push('-H', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
xs.push("-H", quote(`Authorization: Bearer ${request.authentication?.token ?? ""}`));
xs.push(NEWLINE);
} else if (request.authenticationType === 'apikey') {
if (request.authentication?.location === 'query') {
const sep = request.url?.includes('?') ? '&' : '?';
} else if (request.authenticationType === "apikey") {
if (request.authentication?.location === "query") {
const sep = request.url?.includes("?") ? "&" : "?";
request.url = [
request.url,
sep,
encodeURIComponent(request.authentication?.key ?? 'token'),
'=',
encodeURIComponent(request.authentication?.value ?? ''),
].join('');
encodeURIComponent(request.authentication?.key ?? "token"),
"=",
encodeURIComponent(request.authentication?.value ?? ""),
].join("");
} else {
xs.push(
'-H',
"-H",
quote(
`${request.authentication?.key ?? 'X-Api-Key'}: ${request.authentication?.value ?? ''}`,
`${request.authentication?.key ?? "X-Api-Key"}: ${request.authentication?.value ?? ""}`,
),
);
}
@@ -103,13 +103,13 @@ export async function convert(request: Partial<GrpcRequest>, allProtoFiles: stri
// Add form params
if (request.message) {
xs.push('-d', quote(request.message));
xs.push("-d", quote(request.message));
xs.push(NEWLINE);
}
// Add the server address
if (request.url) {
const server = request.url.replace(/^https?:\/\//, ''); // remove protocol
const server = request.url.replace(/^https?:\/\//, ""); // remove protocol
xs.push(server);
xs.push(NEWLINE);
}
@@ -125,7 +125,7 @@ export async function convert(request: Partial<GrpcRequest>, allProtoFiles: stri
xs.splice(xs.length - 1, 1);
}
return xs.join(' ');
return xs.join(" ");
}
function quote(arg: string): string {
@@ -141,7 +141,7 @@ function findParentProtoDir(startPath: string): string | null {
let dir = path.resolve(startPath);
while (true) {
if (path.basename(dir) === 'proto') {
if (path.basename(dir) === "proto") {
return dir;
}
+64 -64
View File
@@ -1,107 +1,107 @@
import { describe, expect, test } from 'vitest';
import { convert } from '../src';
import { describe, expect, test } from "vite-plus/test";
import { convert } from "../src";
describe('exporter-curl', () => {
test('Simple example', async () => {
describe("exporter-curl", () => {
test("Simple example", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
url: "https://yaak.app",
},
[],
),
).toEqual(['grpcurl yaak.app'].join(' \\\n '));
).toEqual(["grpcurl yaak.app"].join(" \\\n "));
});
test('Basic metadata', async () => {
test("Basic metadata", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
url: "https://yaak.app",
metadata: [
{ name: 'aaa', value: 'AAA' },
{ enabled: true, name: 'bbb', value: 'BBB' },
{ enabled: false, name: 'disabled', value: 'ddd' },
{ name: "aaa", value: "AAA" },
{ enabled: true, name: "bbb", value: "BBB" },
{ enabled: false, name: "disabled", value: "ddd" },
],
},
[],
),
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, 'yaak.app'].join(' \\\n '));
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, "yaak.app"].join(" \\\n "));
});
test('Basic auth', async () => {
test("Basic auth", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
authenticationType: 'basic',
url: "https://yaak.app",
authenticationType: "basic",
authentication: {
username: 'user',
password: 'pass',
username: "user",
password: "pass",
},
},
[],
),
).toEqual([`grpcurl -H 'Authorization: Basic dXNlcjpwYXNz'`, 'yaak.app'].join(' \\\n '));
).toEqual([`grpcurl -H 'Authorization: Basic dXNlcjpwYXNz'`, "yaak.app"].join(" \\\n "));
});
test('API key auth', async () => {
test("API key auth", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
authenticationType: 'apikey',
url: "https://yaak.app",
authenticationType: "apikey",
authentication: {
key: 'X-Token',
value: 'tok',
key: "X-Token",
value: "tok",
},
},
[],
),
).toEqual([`grpcurl -H 'X-Token: tok'`, 'yaak.app'].join(' \\\n '));
).toEqual([`grpcurl -H 'X-Token: tok'`, "yaak.app"].join(" \\\n "));
});
test('API key auth', async () => {
test("API key auth", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
authenticationType: 'apikey',
url: "https://yaak.app",
authenticationType: "apikey",
authentication: {
location: 'query',
key: 'token',
value: 'tok 1',
location: "query",
key: "token",
value: "tok 1",
},
},
[],
),
).toEqual(['grpcurl', 'yaak.app?token=tok%201'].join(' \\\n '));
).toEqual(["grpcurl", "yaak.app?token=tok%201"].join(" \\\n "));
});
test('Single proto file', async () => {
expect(await convert({ url: 'https://yaak.app' }, ['/foo/bar/baz.proto'])).toEqual(
test("Single proto file", async () => {
expect(await convert({ url: "https://yaak.app" }, ["/foo/bar/baz.proto"])).toEqual(
[
`grpcurl -import-path '/foo/bar'`,
`-import-path '/foo'`,
`-proto '/foo/bar/baz.proto'`,
'yaak.app',
].join(' \\\n '),
"yaak.app",
].join(" \\\n "),
);
});
test('Multiple proto files, same dir', async () => {
test("Multiple proto files, same dir", async () => {
expect(
await convert({ url: 'https://yaak.app' }, ['/foo/bar/aaa.proto', '/foo/bar/bbb.proto']),
await convert({ url: "https://yaak.app" }, ["/foo/bar/aaa.proto", "/foo/bar/bbb.proto"]),
).toEqual(
[
`grpcurl -import-path '/foo/bar'`,
`-import-path '/foo'`,
`-proto '/foo/bar/aaa.proto'`,
`-proto '/foo/bar/bbb.proto'`,
'yaak.app',
].join(' \\\n '),
"yaak.app",
].join(" \\\n "),
);
});
test('Multiple proto files, different dir', async () => {
test("Multiple proto files, different dir", async () => {
expect(
await convert({ url: 'https://yaak.app' }, ['/aaa/bbb/ccc.proto', '/xxx/yyy/zzz.proto']),
await convert({ url: "https://yaak.app" }, ["/aaa/bbb/ccc.proto", "/xxx/yyy/zzz.proto"]),
).toEqual(
[
`grpcurl -import-path '/aaa/bbb'`,
@@ -110,23 +110,23 @@ describe('exporter-curl', () => {
`-import-path '/xxx'`,
`-proto '/aaa/bbb/ccc.proto'`,
`-proto '/xxx/yyy/zzz.proto'`,
'yaak.app',
].join(' \\\n '),
"yaak.app",
].join(" \\\n "),
);
});
test('Single include dir', async () => {
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb'])).toEqual(
[`grpcurl -import-path '/aaa/bbb'`, 'yaak.app'].join(' \\\n '),
test("Single include dir", async () => {
expect(await convert({ url: "https://yaak.app" }, ["/aaa/bbb"])).toEqual(
[`grpcurl -import-path '/aaa/bbb'`, "yaak.app"].join(" \\\n "),
);
});
test('Multiple include dir', async () => {
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy'])).toEqual(
[`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, 'yaak.app'].join(' \\\n '),
test("Multiple include dir", async () => {
expect(await convert({ url: "https://yaak.app" }, ["/aaa/bbb", "/xxx/yyy"])).toEqual(
[`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, "yaak.app"].join(" \\\n "),
);
});
test('Mixed proto and dirs', async () => {
test("Mixed proto and dirs", async () => {
expect(
await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy', '/foo/bar.proto']),
await convert({ url: "https://yaak.app" }, ["/aaa/bbb", "/xxx/yyy", "/foo/bar.proto"]),
).toEqual(
[
`grpcurl -import-path '/aaa/bbb'`,
@@ -134,45 +134,45 @@ describe('exporter-curl', () => {
`-import-path '/foo'`,
`-import-path '/'`,
`-proto '/foo/bar.proto'`,
'yaak.app',
].join(' \\\n '),
"yaak.app",
].join(" \\\n "),
);
});
test('Sends data', async () => {
test("Sends data", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
message: JSON.stringify({ foo: 'bar', baz: 1.0 }, null, 2),
url: "https://yaak.app",
message: JSON.stringify({ foo: "bar", baz: 1.0 }, null, 2),
},
['/foo.proto'],
["/foo.proto"],
),
).toEqual(
[
`grpcurl -import-path '/'`,
`-proto '/foo.proto'`,
`-d '{\n "foo": "bar",\n "baz": 1\n}'`,
'yaak.app',
].join(' \\\n '),
"yaak.app",
].join(" \\\n "),
);
});
test('Sends data with unresolved template tags', async () => {
test("Sends data with unresolved template tags", async () => {
expect(
await convert(
{
url: 'https://yaak.app',
url: "https://yaak.app",
message: '{"timestamp": ${[ faker "timestamp" ]}, "foo": "bar"}',
},
['/foo.proto'],
["/foo.proto"],
),
).toEqual(
[
`grpcurl -import-path '/'`,
`-proto '/foo.proto'`,
`-d '{"timestamp": \${[ faker "timestamp" ]}, "foo": "bar"}'`,
'yaak.app',
].join(' \\\n '),
"yaak.app",
].join(" \\\n "),
);
});
});