Fix lint errors and show docs explorer on Cmd click

This commit is contained in:
Gregory Schier
2025-07-14 14:52:16 -07:00
parent 6f1fd7a254
commit 0c60d190af
48 changed files with 454 additions and 199 deletions

133
package-lock.json generated
View File

@@ -7839,6 +7839,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fault": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz",
"integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -8030,6 +8043,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/format-graphql": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/format-graphql/-/format-graphql-1.5.0.tgz",
@@ -8987,6 +9008,29 @@
"node": ">= 0.4"
}
},
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"ccount": "^2.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"mdast-util-to-hast": "^13.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"stringify-entities": "^4.0.0",
"zwitch": "^2.0.4"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@@ -9074,6 +9118,16 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/html-void-elements": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/http-reasons": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz",
@@ -10885,6 +10939,36 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-frontmatter": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz",
"integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"escape-string-regexp": "^5.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0",
"micromark-extension-frontmatter": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mdast-util-gfm": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
@@ -11259,6 +11343,22 @@
"micromark-util-types": "^2.0.0"
}
},
"node_modules/micromark-extension-frontmatter": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz",
"integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==",
"license": "MIT",
"dependencies": {
"fault": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
@@ -14587,6 +14687,37 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rehype-stringify": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz",
"integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"hast-util-to-html": "^9.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-frontmatter": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz",
"integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-frontmatter": "^2.0.0",
"micromark-extension-frontmatter": "^2.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-gfm": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
@@ -18626,6 +18757,8 @@
"react-markdown": "^10.1.0",
"react-pdf": "^10.0.1",
"react-use": "^17.6.0",
"rehype-stringify": "^10.0.1",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"slugify": "^1.6.6",
"uuid": "^11.1.0",

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,4 +1,4 @@
import { PluginDefinition } from '@yaakapp/api';
import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
authentication: {

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,4 +1,4 @@
import { PluginDefinition } from '@yaakapp/api';
import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
authentication: {

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonwebtoken": "^9.0.2"

View File

@@ -1,4 +1,4 @@
import { PluginDefinition } from '@yaakapp/api';
import type { PluginDefinition } from '@yaakapp/api';
import jwt from 'jsonwebtoken';
const algorithms = [
@@ -20,49 +20,49 @@ const algorithms = [
const defaultAlgorithm = algorithms[0];
export const plugin: PluginDefinition = {
authentication: {
name: 'jwt',
label: 'JWT Bearer',
shortLabel: 'JWT',
args: [
{
type: 'select',
name: 'algorithm',
label: 'Algorithm',
hideLabel: true,
defaultValue: defaultAlgorithm,
options: algorithms.map(value => ({ label: value === 'none' ? 'None' : value, value })),
},
{
type: 'text',
name: 'secret',
label: 'Secret or Private Key',
password: true,
optional: true,
multiLine: true,
},
{
type: 'checkbox',
name: 'secretBase64',
label: 'Secret is base64 encoded',
},
{
type: 'editor',
name: 'payload',
label: 'Payload',
language: 'json',
defaultValue: '{\n "foo": "bar"\n}',
placeholder: '{ }',
},
],
async onApply(_ctx, { values }) {
const { algorithm, secret: _secret, secretBase64, payload } = values;
const secret = secretBase64 ? Buffer.from(`${_secret}`, 'base64') : `${_secret}`;
const token = jwt.sign(`${payload}`, secret, { algorithm: algorithm as any });
const value = `Bearer ${token}`;
return { setHeaders: [{ name: 'Authorization', value }] };
}
,
authentication: {
name: 'jwt',
label: 'JWT Bearer',
shortLabel: 'JWT',
args: [
{
type: 'select',
name: 'algorithm',
label: 'Algorithm',
hideLabel: true,
defaultValue: defaultAlgorithm,
options: algorithms.map((value) => ({ label: value === 'none' ? 'None' : value, value })),
},
{
type: 'text',
name: 'secret',
label: 'Secret or Private Key',
password: true,
optional: true,
multiLine: true,
},
{
type: 'checkbox',
name: 'secretBase64',
label: 'Secret is base64 encoded',
},
{
type: 'editor',
name: 'payload',
label: 'Payload',
language: 'json',
defaultValue: '{\n "foo": "bar"\n}',
placeholder: '{ }',
},
],
async onApply(_ctx, { values }) {
const { algorithm, secret: _secret, secretBase64, payload } = values;
const secret = secretBase64 ? Buffer.from(`${_secret}`, 'base64') : `${_secret}`;
const token = jwt.sign(`${payload}`, secret, {
algorithm: algorithm as (typeof algorithms)[number],
});
const value = `Bearer ${token}`;
return { setHeaders: [{ name: 'Authorization', value }] };
},
}
;
},
};

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,4 +1,4 @@
import { Context } from '@yaakapp/api';
import type { Context } from '@yaakapp/api';
export async function storeToken(
ctx: Context,

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonpath-plus": "^10.3.0"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"@xmldom/xmldom": "^0.9.8",

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"shell-quote": "^1.8.1"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"yaml": "^2.4.2"

View File

@@ -4,11 +4,11 @@ export function convertSyntax(variable: string): string {
return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}');
}
export function isJSObject(obj: any) {
export function isJSObject(obj: unknown) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
export function isJSString(obj: any) {
export function isJSString(obj: unknown) {
return Object.prototype.toString.call(obj) === '[object String]';
}

View File

@@ -1,4 +1,4 @@
import { Context, PluginDefinition } from '@yaakapp/api';
import type { Context, PluginDefinition } from '@yaakapp/api';
import YAML from 'yaml';
import { deleteUndefinedAttrs, isJSObject } from './common';
import { convertInsomniaV4 } from './v4';
@@ -15,16 +15,18 @@ export const plugin: PluginDefinition = {
};
export function convertInsomnia(contents: string) {
let parsed: any;
let parsed: unknown;
try {
parsed = JSON.parse(contents);
} catch (e) {
} catch {
// Fall through
}
try {
parsed = parsed ?? YAML.parse(contents);
} catch (e) {
} catch {
// Fall through
}
if (!isJSObject(parsed)) return null;

View File

@@ -1,7 +1,8 @@
import { PartialImportResources } from '@yaakapp/api';
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { PartialImportResources } from '@yaakapp/api';
import { convertId, convertSyntax, isJSObject } from './common';
export function convertInsomniaV4(parsed: Record<string, any>) {
export function convertInsomniaV4(parsed: any) {
if (!Array.isArray(parsed.resources)) return null;
const resources: PartialImportResources = {
@@ -14,7 +15,9 @@ export function convertInsomniaV4(parsed: Record<string, any>) {
};
// Import workspaces
const workspacesToImport = parsed.resources.filter(r => isJSObject(r) && r._type === 'workspace');
const workspacesToImport = parsed.resources.filter(
(r: any) => isJSObject(r) && r._type === 'workspace',
);
for (const w of workspacesToImport) {
resources.workspaces.push({
id: convertId(w._id),
@@ -40,13 +43,9 @@ export function convertInsomniaV4(parsed: Record<string, any>) {
resources.folders.push(importFolder(child, w._id));
nextFolder(child._id);
} else if (child._type === 'request') {
resources.httpRequests.push(
importHttpRequest(child, w._id),
);
resources.httpRequests.push(importHttpRequest(child, w._id));
} else if (child._type === 'grpc_request') {
resources.grpcRequests.push(
importGrpcRequest(child, w._id),
);
resources.grpcRequests.push(importGrpcRequest(child, w._id));
}
}
};
@@ -64,10 +63,7 @@ export function convertInsomniaV4(parsed: Record<string, any>) {
return { resources };
}
function importHttpRequest(
r: any,
workspaceId: string,
): PartialImportResources['httpRequests'][0] {
function importHttpRequest(r: any, workspaceId: string): PartialImportResources['httpRequests'][0] {
let bodyType: string | null = null;
let body = {};
if (r.body.mimeType === 'application/octet-stream') {
@@ -141,10 +137,7 @@ function importHttpRequest(
};
}
function importGrpcRequest(
r: any,
workspaceId: string,
): PartialImportResources['grpcRequests'][0] {
function importGrpcRequest(r: any, workspaceId: string): PartialImportResources['grpcRequests'][0] {
const parts = r.protoMethodName.split('/').filter((p: any) => p !== '');
const service = parts[0] ?? null;
const method = parts[1] ?? null;
@@ -186,13 +179,18 @@ function importFolder(f: any, workspaceId: string): PartialImportResources['fold
};
}
function importEnvironment(e: any, workspaceId: string, isParent?: boolean): PartialImportResources['environments'][0] {
function importEnvironment(
e: any,
workspaceId: string,
isParent?: boolean,
): PartialImportResources['environments'][0] {
return {
id: convertId(e._id),
createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined,
updatedAt: e.modified ? new Date(e.modified).toISOString().replace('Z', '') : undefined,
workspaceId: convertId(workspaceId),
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
sortPriority: e.metaSortKey, // Will be added to Yaak later
base: isParent ?? e.parentId === workspaceId,
model: 'environment',

View File

@@ -1,8 +1,16 @@
import { PartialImportResources } from '@yaakapp/api';
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { PartialImportResources } from '@yaakapp/api';
import { convertId, convertSyntax, isJSObject } from './common';
export function convertInsomniaV5(parsed: Record<string, any>) {
if (!Array.isArray(parsed.collection)) return null;
export function convertInsomniaV5(parsed: any) {
// Assert parsed is object
if (parsed == null || typeof parsed !== 'object') {
return null;
}
if (!('collection' in parsed) || !Array.isArray(parsed.collection)) {
return null;
}
const resources: PartialImportResources = {
environments: [],
@@ -14,7 +22,7 @@ export function convertInsomniaV5(parsed: Record<string, any>) {
};
// Import workspaces
const meta: Record<string, any> = parsed.meta ?? {};
const meta = ('meta' in parsed ? parsed.meta : {}) as Record<string, any>;
resources.workspaces.push({
id: convertId(meta.id ?? 'collection'),
createdAt: meta.created ? new Date(meta.created).toISOString().replace('Z', '') : undefined,
@@ -36,17 +44,11 @@ export function convertInsomniaV5(parsed: Record<string, any>) {
resources.folders.push(importFolder(child, meta.id, parentId));
nextFolder(child.children, child.meta.id);
} else if (child.method) {
resources.httpRequests.push(
importHttpRequest(child, meta.id, parentId),
);
resources.httpRequests.push(importHttpRequest(child, meta.id, parentId));
} else if (child.protoFileId) {
resources.grpcRequests.push(
importGrpcRequest(child, meta.id, parentId),
);
resources.grpcRequests.push(importGrpcRequest(child, meta.id, parentId));
} else if (child.url) {
resources.websocketRequests.push(
importWebsocketRequest(child, meta.id, parentId),
);
resources.websocketRequests.push(importWebsocketRequest(child, meta.id, parentId));
}
}
};
@@ -219,7 +221,11 @@ function importAuthentication(r: any) {
return { authenticationType, authentication } as const;
}
function importFolder(f: any, workspaceId: string, parentId: string): PartialImportResources['folders'][0] {
function importFolder(
f: any,
workspaceId: string,
parentId: string,
): PartialImportResources['folders'][0] {
const id = f.meta?.id ?? f._id;
const created = f.meta?.created ?? f.created;
const updated = f.meta?.modified ?? f.updated;
@@ -238,8 +244,11 @@ function importFolder(f: any, workspaceId: string, parentId: string): PartialImp
};
}
function importEnvironment(e: any, workspaceId: string, isParent?: boolean): PartialImportResources['environments'][0] {
function importEnvironment(
e: any,
workspaceId: string,
isParent?: boolean,
): PartialImportResources['environments'][0] {
const id = e.meta?.id ?? e._id;
const created = e.meta?.created ?? e.created;
const updated = e.meta?.modified ?? e.updated;
@@ -251,7 +260,8 @@ function importEnvironment(e: any, workspaceId: string, isParent?: boolean): Par
updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined,
workspaceId: convertId(workspaceId),
public: !e.isPrivate,
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
sortPriority: sortKey, // Will be added to Yaak later
base: isParent ?? e.parentId === workspaceId,
model: 'environment',

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"openapi-to-postmanv2": "^5.0.0",

View File

@@ -8,6 +8,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,11 +1,11 @@
import { Environment, PluginDefinition } from '@yaakapp/api';
import type { Environment, PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
importer: {
name: 'Yaak',
description: 'Yaak official format',
onImport(_ctx, args) {
return migrateImport(args.text) as any;
return migrateImport(args.text);
},
},
};
@@ -14,7 +14,7 @@ export function migrateImport(contents: string) {
let parsed;
try {
parsed = JSON.parse(contents);
} catch (err) {
} catch {
return undefined;
}
@@ -69,6 +69,6 @@ export function migrateImport(contents: string) {
return { resources: parsed.resources }; // Should already be in the correct format
}
function isJSObject(obj: any) {
function isJSObject(obj: unknown) {
return Object.prototype.toString.call(obj) === '[object Object]';
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,4 +1,4 @@
import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
templateFunctions: [

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,4 +1,4 @@
import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
import { createHash, createHmac } from 'node:crypto';
const algorithms = ['md5', 'sha1', 'sha256', 'sha512'] as const;

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonpath-plus": "^10.3.0"

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonpath-plus": "^10.3.0",

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"uuid": "^11.1.0"

View File

@@ -7,7 +7,7 @@ export const plugin: PluginDefinition = {
name: 'uuid.v1',
description: 'Generate a UUID V1',
args: [],
async onRender(_ctx: Context, _args: CallTemplateFunctionArgs): Promise<string | null> {
async onRender(): Promise<string | null> {
return v1();
},
},
@@ -32,7 +32,7 @@ export const plugin: PluginDefinition = {
name: 'uuid.v4',
description: 'Generate a UUID V4',
args: [],
async onRender(_ctx: Context, _args: CallTemplateFunctionArgs): Promise<string | null> {
async onRender(): Promise<string | null> {
return v4();
},
},
@@ -68,7 +68,7 @@ export const plugin: PluginDefinition = {
name: 'uuid.v7',
description: 'Generate a UUID V7',
args: [],
async onRender(_ctx: Context, _args: CallTemplateFunctionArgs): Promise<string | null> {
async onRender(): Promise<string | null> {
return v7();
},
},

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"@xmldom/xmldom": "^0.9.8",

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "tsc --noEmit"
"lint": "eslint . --ext .ts,.tsx"
}
}

View File

@@ -3,11 +3,11 @@ import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import type { CSSProperties } from 'react';
import React from 'react';
import { showGraphQLDocExplorerAtom } from '../atoms/graphqlSchemaAtom';
import { useCurrentGraphQLSchema } from '../hooks/useIntrospectGraphQL';
import type { SlotProps } from './core/SplitLayout';
import { SplitLayout } from './core/SplitLayout';
import { GraphQLDocsExplorer } from './GraphQLDocsExplorer';
import { GraphQLDocsExplorer } from './graphql/GraphQLDocsExplorer';
import { showGraphQLDocExplorerAtom } from './graphql/graphqlAtoms';
import { HttpRequestPane } from './HttpRequestPane';
import { HttpResponsePane } from './HttpResponsePane';
@@ -17,7 +17,6 @@ interface Props {
}
export function HttpRequestLayout({ activeRequest, style }: Props) {
const { bodyType } = activeRequest;
const showGraphQLDocExplorer = useAtomValue(showGraphQLDocExplorerAtom);
const graphQLSchema = useCurrentGraphQLSchema(activeRequest);
@@ -39,11 +38,11 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
/>
);
if (bodyType === 'graphql' && showGraphQLDocExplorer && graphQLSchema != null) {
if (activeRequest.bodyType === 'graphql' && showGraphQLDocExplorer && graphQLSchema != null) {
return (
<SplitLayout
name="graphql_layout"
defaultRatio={1/3}
defaultRatio={1 / 3}
firstSlot={requestResponseSplit}
secondSlot={({ style, orientation }) => (
<GraphQLDocsExplorer

View File

@@ -47,13 +47,13 @@ import type { TabItem } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
import { FormMultipartEditor } from './FormMultipartEditor';
import { FormUrlencodedEditor } from './FormUrlencodedEditor';
import { GraphQLEditor } from './GraphQLEditor';
import { HeadersEditor } from './HeadersEditor';
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
import { MarkdownEditor } from './MarkdownEditor';
import { RequestMethodDropdown } from './RequestMethodDropdown';
import { UrlBar } from './UrlBar';
import { UrlParametersEditor } from './UrlParameterEditor';
import { GraphQLEditor } from './graphql/GraphQLEditor';
interface Props {
style: CSSProperties;

View File

@@ -6,7 +6,9 @@ import {
} from '@codemirror/autocomplete';
import { history, historyKeymap } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import type { LanguageSupport } from '@codemirror/language';
import {
codeFolding,
@@ -35,15 +37,15 @@ import {
import { tags as t } from '@lezer/highlight';
import type { EnvironmentVariable } from '@yaakapp-internal/models';
import { graphql } from 'cm6-graphql';
import { renderMarkdown } from '../../../lib/markdown';
import { pluralizeCount } from '../../../lib/pluralize';
import { showInGraphQLDocsExplorer } from '../../graphql/useGraphQLDocsExplorer';
import type { EditorProps } from './Editor';
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import type { TwigCompletionOption } from './twig/completion';
import { twig } from './twig/extension';
import { pathParametersPlugin } from './twig/pathParameters';
import { json } from '@codemirror/lang-json';
import { xml } from '@codemirror/lang-xml';
import { pairs } from './pairs/extension';
import { url } from './url/extension';
export const syntaxHighlightStyle = HighlightStyle.define([
@@ -123,7 +125,22 @@ export function getLanguageExtension({
// GraphQL is a special exception
if (language === 'graphql') {
return [graphql(), extraExtensions];
return [
graphql(undefined, {
async onCompletionInfoRender(gqlCompletionItem): Promise<Node | null> {
if (!gqlCompletionItem.documentation) return null;
const innerHTML = await renderMarkdown(gqlCompletionItem.documentation);
const span = document.createElement('span');
span.innerHTML = innerHTML;
return span;
},
onShowInDocs(field, type, parentType) {
console.log("SHOW IN DOCS", field);
showInGraphQLDocsExplorer(field, type, parentType).catch(console.error);
},
}),
extraExtensions,
];
}
const base_ = syntaxExtensions[language ?? 'text'] ?? text();
@@ -229,7 +246,6 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
}
},
}),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
rectangularSelection(),
crosshairCursor(),

View File

@@ -64,7 +64,7 @@ export function SplitLayout({
}
const size = useContainerSize(containerRef);
const verticalBasedOnSize = size.width < STACK_VERTICAL_WIDTH;
const verticalBasedOnSize = size.width !== 0 && size.width < STACK_VERTICAL_WIDTH;
const vertical = layout !== 'horizontal' && (layout === 'vertical' || verticalBasedOnSize);
const styles = useMemo<CSSProperties>(() => {
@@ -142,31 +142,25 @@ export function SplitLayout({
[width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth],
);
const containerQueryReady = size.width > 0 || size.height > 0;
return (
<div
ref={containerRef}
style={styles}
className={classNames(className, 'grid w-full h-full overflow-hidden')}
>
{containerQueryReady && (
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
{secondSlot && (
<>
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
{secondSlot && (
<>
<ResizeHandle
style={areaD}
isResizing={isResizing}
className={classNames(vertical ? '-translate-y-1.5' : '-translate-x-1.5')}
onResizeStart={handleResizeStart}
onReset={handleReset}
side={vertical ? 'top' : 'left'}
justify="center"
/>
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
</>
)}
<ResizeHandle
style={areaD}
isResizing={isResizing}
className={classNames(vertical ? '-translate-y-1.5' : '-translate-x-1.5')}
onResizeStart={handleResizeStart}
onReset={handleReset}
side={vertical ? 'top' : 'left'}
justify="center"
/>
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
</>
)}
</div>

View File

@@ -23,17 +23,18 @@ import {
} from 'graphql';
import type { CSSProperties, HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
import { Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
import { showGraphQLDocExplorerAtom } from '../atoms/graphqlSchemaAtom';
import { useClickOutside } from '../hooks/useClickOutside';
import { useContainerSize } from '../hooks/useContainerQuery';
import { useDebouncedValue } from '../hooks/useDebouncedValue';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
import { jotaiStore } from '../lib/jotai';
import { CountBadge } from './core/CountBadge';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { PlainInput } from './core/PlainInput';
import { Markdown } from './Markdown';
import { useClickOutside } from '../../hooks/useClickOutside';
import { useContainerSize } from '../../hooks/useContainerQuery';
import { useDebouncedValue } from '../../hooks/useDebouncedValue';
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import { jotaiStore } from '../../lib/jotai';
import { CountBadge } from '../core/CountBadge';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';
import { PlainInput } from '../core/PlainInput';
import { Markdown } from '../Markdown';
import { showGraphQLDocExplorerAtom } from './graphqlAtoms';
import { useGraphQLDocsExplorerEvent } from './useGraphQLDocsExplorer';
interface Props {
style?: CSSProperties;
@@ -58,6 +59,17 @@ export const GraphQLDocsExplorer = memo(function GraphQLDocsExplorer({
const mutType = schema.getMutationType();
const subType = schema.getSubscriptionType();
useGraphQLDocsExplorerEvent('gql_docs_explorer.show_in_docs', ({ field }) => {
walkTypeGraph(schema, null, (t) => {
if (t.name === field) {
setActiveItem(toExplorerItem(t, null));
return false;
} else {
return true;
}
});
});
const qryItem: ExplorerItem = qryType ? { kind: 'type', type: qryType, from: null } : null;
const mutItem: ExplorerItem = mutType ? { kind: 'type', type: mutType, from: null } : null;
const subItem: ExplorerItem = subType ? { kind: 'type', type: subType, from: null } : null;
@@ -642,22 +654,20 @@ function GqlSchemaSearch({
const results = useMemo(() => {
const results: SearchResult[] = [];
walkTypeGraph(
currentItem?.type ?? null,
(type, from, depth) => {
if (type === currentItem?.type) {
return null; // Remove the current type from results
}
walkTypeGraph(schema, currentItem?.type ?? null, (type, from, depth) => {
if (type === currentItem?.type) {
return true; // Skip the current type and continue
}
const match = fuzzyMatch(type.name, debouncedValue);
if (match == null) {
// Do nothing
} else {
results.push({ name: type.name, type, score: match.score, from, depth });
}
},
schema,
);
const match = fuzzyMatch(type.name, debouncedValue);
if (match == null) {
// Do nothing
} else {
results.push({ name: type.name, type, score: match.score, from, depth });
}
return true; // Continue searching
});
results.sort((a, b) => {
if (value == '') {
if (a.name.startsWith('_') && !b.name.startsWith('_')) {
@@ -831,13 +841,13 @@ function DocMarkdown({ children, className }: { children: string | null; classNa
}
function walkTypeGraph(
schema: GraphQLSchema,
start: GraphQLType | GraphQLField<any, any> | GraphQLInputField | null,
cb: (
type: GraphQLNamedType | GraphQLField<any, any> | GraphQLInputField,
from: GraphQLNamedType | null,
path: string[],
) => void,
schema: GraphQLSchema,
) => boolean,
) {
const visited = new Set<string>();
const queue: Array<{
@@ -867,7 +877,8 @@ function walkTypeGraph(
if (visited.has(name)) continue;
visited.add(name);
cb(current, from, path);
const cont = cb(current, from, path);
if (!cont) break;
if (isObjectType(current) || isInterfaceType(current)) {
for (const field of Object.values(current.getFields())) {

View File

@@ -6,19 +6,19 @@ import { formatSdl } from 'format-graphql';
import { useAtom } from 'jotai';
import { useEffect, useMemo, useRef } from 'react';
import { useLocalStorage } from 'react-use';
import { showGraphQLDocExplorerAtom } from '../atoms/graphqlSchemaAtom';
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
import { showDialog } from '../lib/dialog';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import type { EditorProps } from './core/Editor/Editor';
import { Editor } from './core/Editor/Editor';
import { FormattedError } from './core/FormattedError';
import { Icon } from './core/Icon';
import { Separator } from './core/Separator';
import { useIntrospectGraphQL } from '../../hooks/useIntrospectGraphQL';
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import { showDialog } from '../../lib/dialog';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import type { DropdownItem } from '../core/Dropdown';
import { Dropdown } from '../core/Dropdown';
import type { EditorProps } from '../core/Editor/Editor';
import { Editor } from '../core/Editor/Editor';
import { FormattedError } from '../core/FormattedError';
import { Icon } from '../core/Icon';
import { Separator } from '../core/Separator';
import { showGraphQLDocExplorerAtom } from './graphqlAtoms';
type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> & {
baseRequest: HttpRequest;

View File

@@ -1,3 +1,3 @@
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage';
import { atomWithKVStorage } from '../../lib/atoms/atomWithKVStorage';
export const showGraphQLDocExplorerAtom = atomWithKVStorage<boolean>('show_graphql_docs', false);

View File

@@ -0,0 +1,63 @@
import EventEmitter from 'eventemitter3';
import type { DependencyList } from 'react';
import { useEffect } from 'react';
import { jotaiStore } from '../../lib/jotai';
import { sleep } from '../../lib/sleep';
import { showGraphQLDocExplorerAtom } from './graphqlAtoms';
type EventDataMap = {
'gql_docs_explorer.show_in_docs': { field?: string; type?: string; parentType?: string };
'gql_docs_explorer.focus_tab': undefined;
};
export function useGraphQLDocsExplorerEvent<
Event extends keyof EventDataMap,
Data extends EventDataMap[Event],
>(event: Event, fn: (data: Data) => void, deps?: DependencyList) {
useEffect(() => {
emitter.on(event, fn);
return () => {
emitter.off(event, fn);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}
export async function showInGraphQLDocsExplorer(
field: string | undefined,
type: string | undefined,
parentType: string | undefined,
) {
const isVisible = jotaiStore.get(showGraphQLDocExplorerAtom);
if (!isVisible) {
// Show and give some time for the explorer to start listening for events
jotaiStore.set(showGraphQLDocExplorerAtom, true);
await sleep(100);
}
emitter.emit('gql_docs_explorer.show_in_docs', { field, type, parentType });
}
const emitter = new (class GraphQLDocsExplorerEventEmitter {
#emitter: EventEmitter = new EventEmitter();
emit<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
event: Event,
data: Data,
) {
this.#emitter.emit(event, data);
}
on<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
event: Event,
fn: (data: Data) => void,
) {
this.#emitter.on(event, fn);
}
off<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
event: Event,
fn: (data: Data) => void,
) {
this.#emitter.off(event, fn);
}
})();

27
src-web/lib/markdown.ts Normal file
View File

@@ -0,0 +1,27 @@
import rehypeStringify from 'rehype-stringify';
import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import { unified } from 'unified';
const renderer = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype, {
// handlers: {
// link: (state, node, parent) => {
// return node;
// },
// },
})
.use(rehypeStringify);
export async function renderMarkdown(md: string): Promise<string> {
try {
const r = await renderer.process(md);
return r.toString();
} catch (err) {
console.log('FAILED TO RENDER MARKDOWN', err);
return 'error';
}
}

View File

@@ -6,7 +6,7 @@
"scripts": {
"dev": "vite dev --force",
"build": "vite build",
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"@codemirror/commands": "^6.8.1",
@@ -61,6 +61,8 @@
"react-markdown": "^10.1.0",
"react-pdf": "^10.0.1",
"react-use": "^17.6.0",
"rehype-stringify": "^10.0.1",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"slugify": "^1.6.6",
"uuid": "^11.1.0",