Skip to content

Instantly share code, notes, and snippets.

@ekoneko
Last active March 15, 2023 07:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ekoneko/846e6c42cca345de78ca1b2c63426a12 to your computer and use it in GitHub Desktop.
Save ekoneko/846e6c42cca345de78ca1b2c63426a12 to your computer and use it in GitHub Desktop.
A tampermonkey script for translating yapi to typescript and mock fake data
// ==UserScript==
// @name YApi to Ts Interface
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Generate ts interface or fake data in yapi
// @author Eko
// @match {HOST}/project/*/interface/api/*
// @icon https://www.google.com/s2/favicons?domain=TO_BE_REPLACED
// @grant none
// ==/UserScript==
(async () => {
async function loadScripts() {
async function loadScripts(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.documentElement.appendChild(script);
});
}
/**
* prettier scripts must be loaded in order
*/
await loadScripts("https://unpkg.com/prettier@2/standalone.js");
await loadScripts("https://unpkg.com/prettier@2/parser-typescript.js");
await loadScripts("https://unpkg.com/prettier@2/parser-babel.js");
await loadScripts(
"https://cdn.githubraw.com/bcherny/json-schema-to-typescript-browser/master/bundle.js"
);
const formatWithPrettier = prettier.format.bind(prettier);
// standalone requires parsers be explicitly loaded
prettier.format = (str, options) =>
formatWithPrettier(
str,
Object.assign({}, options, {
plugins: Object.assign({}, prettierPlugins, options.plugins),
})
);
}
async function loadProject() {
const id = location.pathname.match(/\/project\/(\d+)/)?.[1] ?? null;
if (!id) {
throw new Error("project id not found")
}
const res = await fetch(`/api/project/get?id=${id}`)
const data = await res.json();
if (!data.data) {
throw new Error("fetch failed");
}
return data.data;
}
async function loadApi() {
const id = location.pathname.match(/\/api\/(\d+)/)?.[1] ?? null;
if (!id) {
throw new Error("api id not found")
}
const res = await fetch(
`/api/interface/get?id=${id}`
);
const data = await res.json();
if (!data.data) {
throw new Error("fetch failed");
}
return data.data;
}
function upperCaseFirstLetter(text) {
if (!text) {
return '';
}
const chars = Array.from(text.replace(/[^\w]/g, ''));
const name = [chars[0].toUpperCase(), ...chars.slice(1)].join("");
return name;
}
function getInterfaceName(data, action) {
return `${upperCaseFirstLetter(data.method.toLowerCase())}${upperCaseFirstLetter(data.path.split('/').map(upperCaseFirstLetter).join(''))}${action}`
}
const jsttOptions = {
bannerComment: `/**
* The content of this file has been generated automatically using json-schema-to-typescript.
* DO NOT EDIT IT MANUALLY. Instead, make changes to the source JSONSchema file
* and regenerate this file using json-schema-to-typescript.
*
* API Page: ${location.href}
*/`,
};
async function getAndTransformData() {
const [apiData, projectData] = await Promise.all([
loadApi(),
loadProject(),
])
const texts = [];
let mockContent =
`createRule(['${apiData.method}', '${projectData.basepath}${apiData.path}'], function (req, res) {
res.send(204);
})`
if (apiData.res_body_is_json_schema && apiData.res_body_type === "json") {
try {
const resTs = await window.jstt.compile(
JSON.parse(apiData.res_body),
getInterfaceName(apiData, 'Response'),
jsttOptions
);
texts.push(resTs);
mockContent = mockContent.replace('res.send(204)', `res.send(this.jsf.generate(${apiData.res_body}))`);
} catch (err) { console.error(err) }
}
if (apiData.req_body_is_json_schema && apiData.req_body_type === "json") {
try {
const reqTs = await window.jstt.compile(
JSON.parse(apiData.req_body_other),
getInterfaceName(apiData, 'Request'),
jsttOptions
);
texts.push(reqTs);
} catch (err) { console.error(err) }
}
if (texts.length > 0) {
const text = texts.join("\n");
const el = document.createElement('div');
const generateTSIElement = document.createElement('div');
generateTSIElement.onclick = () => navigator.clipboard.writeText(text);
generateTSIElement.innerHTML = "Copy Typescript Interface";
Object.assign(generateTSIElement.style, {
position: "fixed",
bottom: "80px",
left: "10px",
zIndex: 100,
padding: "8px 16px",
background: "#fff",
cursor: "pointer",
});
const generateFakeElement = document.createElement('div');
generateFakeElement.onclick = () => navigator.clipboard.writeText(mockContent);
generateFakeElement.innerHTML = "Copy fake code";
Object.assign(generateFakeElement.style, {
position: "fixed",
bottom: "40px",
left: "10px",
zIndex: 100,
padding: "8px 16px",
background: "#fff",
cursor: "pointer",
});
el.appendChild(generateTSIElement);
el.appendChild(generateFakeElement);
document.body.appendChild(el);
console.log("inject button", el);
return el;
}
}
await loadScripts();
let elRef;
window.history.pushState = new Proxy(window.history.pushState, {
apply: (target, thisArg, argArray) => {
const response = target.apply(thisArg, argArray);
if (elRef && elRef.parentElement) {
elRef.parentElement.remove(elRef);
}
elRef = getAndTransformData();
return response;
},
});
getAndTransformData()
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment