├── static ├── google32d49c2c4264727e.html ├── bartender.png ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── site.webmanifest ├── global.css ├── logo.svg ├── script │ └── load-editor.js ├── butler.svg ├── fonts │ └── poppins.css └── github.svg ├── doc └── assets │ ├── buttons.png │ ├── landing.png │ ├── IlustrationQuestion.png │ └── Butlemock-bad-formatting.jpeg ├── .vscode ├── extensions.json └── settings.json ├── dev.ts ├── .gitignore ├── core ├── utils │ ├── randNumber.ts │ ├── dynamicValue.ts │ ├── typeValidation.ts │ └── strings.ts ├── constants │ └── ValidTypes.ts └── class │ ├── TypeMocker.ts │ └── TypeMocker.test.ts ├── main.ts ├── components └── EditorResponse.tsx ├── twind.config.ts ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── FUNDING.yml ├── routes ├── _app.tsx ├── api │ ├── mock │ │ ├── _middleware.ts │ │ └── index.ts │ └── typemocker │ │ └── index.ts ├── _404.tsx └── index.tsx ├── fresh.gen.ts ├── deno.json ├── LICENSE ├── README.md └── islands └── EditorView.tsx /static/google32d49c2c4264727e.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google32d49c2c4264727e.html -------------------------------------------------------------------------------- /static/bartender.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/bartender.png -------------------------------------------------------------------------------- /doc/assets/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/doc/assets/buttons.png -------------------------------------------------------------------------------- /doc/assets/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/doc/assets/landing.png -------------------------------------------------------------------------------- /static/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/favicon/favicon.ico -------------------------------------------------------------------------------- /static/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /doc/assets/IlustrationQuestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/doc/assets/IlustrationQuestion.png -------------------------------------------------------------------------------- /static/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /doc/assets/Butlemock-bad-formatting.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/doc/assets/Butlemock-bad-formatting.jpeg -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "denoland.vscode-deno", 4 | "sastan.twind-intellisense" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /static/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devrax/Butlermock/HEAD/static/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /dev.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run -A --watch=static/,routes/ 2 | 3 | import dev from "$fresh/dev.ts"; 4 | 5 | await dev(import.meta.url, "./main.ts"); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dotenv environment variable files 2 | .env 3 | .env.development.local 4 | .env.test.local 5 | .env.production.local 6 | .env.local 7 | 8 | .DS_STORE -------------------------------------------------------------------------------- /core/utils/randNumber.ts: -------------------------------------------------------------------------------- 1 | export const rand = (max: number, min = 1) => { 2 | min = Math.ceil(min); 3 | max = Math.floor(max); 4 | return Math.floor(Math.random() * (max - min) + min); 5 | }; -------------------------------------------------------------------------------- /static/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /core/constants/ValidTypes.ts: -------------------------------------------------------------------------------- 1 | const allowedNonPrimitives = ['Date', 'any']; 2 | const allowedArrayNonPrimitives = ['Date[]', 'any[]']; 3 | const allowedPrimitives = ['string', 'number', 'boolean', 'bigint', 'null', 'undefined']; // TODO: In the future I will check for symbol 4 | const allowedArrayPrimitives = ['string[]', 'number[]', 'boolean[]', 'bigint[]', 'null[]', 'undefined[]'] 5 | export const validTypes = [...allowedPrimitives, ...allowedNonPrimitives, ...allowedArrayPrimitives, ...allowedArrayNonPrimitives] -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "editor.defaultFormatter": "denoland.vscode-deno", 5 | "[typescriptreact]": { 6 | "editor.defaultFormatter": "denoland.vscode-deno" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "denoland.vscode-deno" 10 | }, 11 | "[javascriptreact]": { 12 | "editor.defaultFormatter": "denoland.vscode-deno" 13 | }, 14 | "[javascript]": { 15 | "editor.defaultFormatter": "denoland.vscode-deno" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import "$std/dotenv/load.ts"; 8 | 9 | import { start } from "$fresh/server.ts"; 10 | import manifest from "./fresh.gen.ts"; 11 | 12 | import twindPlugin from "$fresh/plugins/twind.ts"; 13 | import twindConfig from "./twind.config.ts"; 14 | 15 | await start(manifest, { plugins: [twindPlugin(twindConfig)] }); 16 | -------------------------------------------------------------------------------- /components/EditorResponse.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "preact/hooks" 2 | 3 | declare var previewer: any; 4 | 5 | interface EditorResponseProps { 6 | codeToShow: string; 7 | } 8 | 9 | export default function EditorResponse({ codeToShow }: EditorResponseProps) { 10 | 11 | useEffect(() => { 12 | previewer?.setValue(codeToShow); 13 | }, [codeToShow]); 14 | 15 | return ( 16 | <> 17 |
18 | 19 | ) 20 | } -------------------------------------------------------------------------------- /twind.config.ts: -------------------------------------------------------------------------------- 1 | import { Options } from "$fresh/plugins/twind.ts"; 2 | import * as Colors from "twind/colors"; 3 | 4 | export default { 5 | selfURL: import.meta.url, 6 | theme: { 7 | colors: { 8 | ...Colors, 9 | 'monaco': { 10 | '50': '#f6f6f6', 11 | '100': '#e7e7e7', 12 | '200': '#d1d1d1', 13 | '300': '#b0b0b0', 14 | '400': '#888888', 15 | '500': '#6d6d6d', 16 | '600': '#5d5d5d', 17 | '700': '#4f4f4f', 18 | '800': '#454545', 19 | '900': '#3d3d3d', 20 | '950': '#1e1e1e', 21 | } 22 | } 23 | } 24 | } as Options; 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "$fresh/server.ts"; 2 | import { Head } from "$fresh/runtime.ts"; 3 | 4 | export default function App({ Component }: AppProps) { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Devrafx] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /routes/api/mock/_middleware.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareHandlerContext } from "$fresh/server.ts"; 2 | 3 | interface State { 4 | data: string; 5 | } 6 | 7 | const allowCORS = (resp: Response, req: Request) => { 8 | let handler = resp; 9 | resp.headers.set("Access-Control-Allow-Origin", "*"); // Allow any origin to access the resource 10 | resp.headers.set("Access-Control-Allow-Methods", "POST"); 11 | resp.headers.set("Access-Control-Allow-Headers", "Content-Type"); 12 | 13 | if (req.method === "OPTIONS") { 14 | handler = new Response(resp.body, { status: 204, headers: resp.headers }); 15 | } 16 | 17 | return handler; 18 | }; 19 | 20 | export async function handler( 21 | req: Request, 22 | ctx: MiddlewareHandlerContext, 23 | ) { 24 | return allowCORS(await ctx.next(), req); 25 | } 26 | -------------------------------------------------------------------------------- /static/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Poppins', Arial, Helvetica, sans-serif; 3 | } 4 | 5 | button, select { 6 | -webkit-tap-highlight-color: none; 7 | outline: none; 8 | } 9 | 10 | button:focus-within, button:focus, select:focus, select:focus-within, input:focus, input:focus-within { 11 | outline: none; 12 | } 13 | 14 | button[disabled] { 15 | opacity: 0.5; 16 | user-select: none; 17 | cursor: none; 18 | pointer-events: none; 19 | } 20 | 21 | #container:not(:empty) + #loader-for-monaco { 22 | display: none; 23 | } 24 | 25 | @media screen and (min-width: 1536px) { 26 | #bmc-wbtn { 27 | transform: scale(1.5) translate(-20px, -20px); 28 | } 29 | 30 | #bmc-wbtn > img[alt="Buy Me A Coffee"] { 31 | transform: scale(1.3); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /routes/_404.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Head } from "$fresh/runtime.ts"; 3 | 4 | export default function Error404() { 5 | return ( 6 | <> 7 | 8 | 404 - Page not found 9 | 10 |
11 |
12 | the fresh logo: a sliced lemon dripping with juice 19 |

404 - Page not found

20 |

21 | The page you were looking for doesn't exist. 22 |

23 | Go back home 24 |
25 |
26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Link to codesandbox where can see the bug or steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - Browser [e.g. chrome 89, safari 14] 28 | - OS: [e.g. iOS 14.7] 29 | - Mobile device (eg iPhone, Galaxy etc) 30 | 31 | **Additional context** 32 | Add any other context about the problem here. -------------------------------------------------------------------------------- /fresh.gen.ts: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is generated by fresh. 2 | // This file SHOULD be checked into source version control. 3 | // This file is automatically updated during development when running `dev.ts`. 4 | 5 | import * as $0 from "./routes/_404.tsx"; 6 | import * as $1 from "./routes/_app.tsx"; 7 | import * as $2 from "./routes/api/mock/_middleware.ts"; 8 | import * as $3 from "./routes/api/mock/index.ts"; 9 | import * as $4 from "./routes/api/typemocker/index.ts"; 10 | import * as $5 from "./routes/index.tsx"; 11 | import * as $$0 from "./islands/EditorView.tsx"; 12 | 13 | const manifest = { 14 | routes: { 15 | "./routes/_404.tsx": $0, 16 | "./routes/_app.tsx": $1, 17 | "./routes/api/mock/_middleware.ts": $2, 18 | "./routes/api/mock/index.ts": $3, 19 | "./routes/api/typemocker/index.ts": $4, 20 | "./routes/index.tsx": $5, 21 | }, 22 | islands: { 23 | "./islands/EditorView.tsx": $$0, 24 | }, 25 | baseUrl: import.meta.url, 26 | }; 27 | 28 | export default manifest; 29 | -------------------------------------------------------------------------------- /static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "lock": false, 3 | "tasks": { 4 | "start": "deno run -A --watch=static/,routes/ dev.ts", 5 | "update": "deno run -A -r https://fresh.deno.dev/update ." 6 | }, 7 | "lint": { 8 | "rules": { 9 | "tags": [ 10 | "fresh", 11 | "recommended" 12 | ] 13 | } 14 | }, 15 | "imports": { 16 | "$fresh/": "https://deno.land/x/fresh@1.3.1/", 17 | "preact": "https://esm.sh/preact@10.15.1", 18 | "preact/": "https://esm.sh/preact@10.15.1/", 19 | "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.0", 20 | "@preact/signals": "https://esm.sh/*@preact/signals@1.1.3", 21 | "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3", 22 | "twind": "https://esm.sh/twind@0.16.19", 23 | "twind/": "https://esm.sh/twind@0.16.19/", 24 | "$std/": "https://deno.land/std@0.193.0/", 25 | "@core/": "./core/", 26 | "@private/": "./private/" 27 | }, 28 | "compilerOptions": { 29 | "jsx": "react-jsx", 30 | "jsxImportSource": "preact" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2023 Rafael Alexander Mejia Blanco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the “Software”), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /routes/api/mock/index.ts: -------------------------------------------------------------------------------- 1 | import Interface2Mock from '@core/class/TypeMocker.ts'; 2 | import { Handlers } from "$fresh/server.ts"; 3 | 4 | export const handler: Handlers = { 5 | async POST(req) { 6 | try { 7 | const content = await req.json(); 8 | 9 | if(content.quantity && !isNaN(content.quantity) && content.quantity > 1) { 10 | if(content.quantity > 10) throw new Error('Exceeded quantity, must be lower than 10 objects'); 11 | const mocks = []; 12 | for(let i = 0; i < content.quantity; i++) { 13 | const buildInterface = new Interface2Mock(content.value, JSON.parse(content.valueForAny)); 14 | mocks.push(buildInterface.buildMock(content.mustReturn ?? '')); 15 | } 16 | return new Response(JSON.stringify(mocks), { status: 200, headers: { 'Content-type': 'application/json'}}); 17 | } 18 | 19 | const prepareInterface = new Interface2Mock(content.value, JSON.parse(content.valueForAny)); 20 | 21 | return new Response(JSON.stringify(prepareInterface.buildMock(content.mustReturn ?? '')), { status: 200, headers: { 'Content-type': 'application/json'}}); 22 | } catch(err) { 23 | console.error(err); 24 | return new Response(JSON.stringify({ error: err?.message }), { status: 500, headers: { 'Content-Type': 'application/json'}}); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /static/script/load-editor.js: -------------------------------------------------------------------------------- 1 | // require is provided by loader.min.js. 2 | var theEditor = null; 3 | var previewer = null; 4 | require.config({ 5 | paths: { 6 | vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs", 7 | }, 8 | }); 9 | 10 | document.addEventListener("DOMContentLoaded", () => { 11 | require(["vs/editor/editor.main"], () => { 12 | theEditor = monaco.editor.create(document.getElementById("container"), { 13 | language: "typescript", 14 | theme: "vs-dark", 15 | autoIndent: true, 16 | formatOnPaste: true, 17 | minimap: { 18 | enabled: false, 19 | }, 20 | }); 21 | 22 | theEditor.setValue(`interface Example { 23 | image: string; 24 | gravatar_id: string; 25 | url: string; 26 | fixedUrl: "https\\://gleeful-anterior.com"; 27 | fullName: string; 28 | location: string; 29 | email?: string; 30 | hireable: boolean; 31 | twitter_username?: string; 32 | following: number; 33 | created_at: Date; 34 | chon: string; 35 | } 36 | 37 | interface Hello extends Example { 38 | chin: string; 39 | } 40 | 41 | interface World extends Example { 42 | chon: number; 43 | }`); 44 | 45 | previewer = monaco.editor.create(document.getElementById("preview-code"), { 46 | value: ``, 47 | language: "json", 48 | theme: "vs-dark", 49 | readOnly: true, 50 | minimap: { 51 | enabled: false, 52 | }, 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /routes/api/typemocker/index.ts: -------------------------------------------------------------------------------- 1 | import Interface2Mock from '@core/class/TypeMocker.ts'; 2 | import { Handlers } from "$fresh/server.ts"; 3 | 4 | export const handler: Handlers = { 5 | async POST(req) { 6 | const content = await req.json(); 7 | try { 8 | 9 | if(content.quantity && !isNaN(content.quantity) && content.quantity > 1) { 10 | if(content.quantity > 10) throw new Error('Exceeded quantity, must be lower than 10 objects'); 11 | const mocks = []; 12 | for(let i = 0; i < content.quantity; i++) { 13 | const buildInterface = new Interface2Mock(content.value, JSON.parse(content.valueForAny)); 14 | mocks.push(buildInterface.buildMock(content.mustReturn)); 15 | } 16 | return new Response(JSON.stringify(mocks), { status: 200, headers: { 'Content-type': 'application/json'}}); 17 | } 18 | 19 | const prepareInterface = new Interface2Mock(content.value, JSON.parse(content.valueForAny)); 20 | 21 | return new Response(JSON.stringify(prepareInterface.buildMock(content.mustReturn ?? '')), { status: 200, headers: { 'Content-type': 'application/json'}}); 22 | } catch(err) { 23 | console.log('Error for debugging'+ content.value); 24 | console.error(err); 25 | return new Response(JSON.stringify({ error: err?.message }), { status: 500, headers: { 'Content-Type': 'application/json'}}); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /core/utils/dynamicValue.ts: -------------------------------------------------------------------------------- 1 | import { fakerEN } from "https://esm.sh/@faker-js/faker@8.0.2"; 2 | import StringPlaceholder from "@core/utils/strings.ts"; 3 | import { rand } from "@core/utils/randNumber.ts"; 4 | const faker = fakerEN; 5 | 6 | const forStrings = new StringPlaceholder(faker); 7 | 8 | export const checkConstant = (name: string, type: string, anyReturn: unknown = null) => { 9 | switch(type) { 10 | // Primitives 11 | case "string": 12 | return forStrings.checkStringName(name) ?? faker.lorem.slug({ min: 1, max: rand(5, 2)}); 13 | case "boolean": 14 | return Boolean(rand(2,0)); 15 | case "number": 16 | case "bigInt": 17 | return faker.number[type === "bigInt" ? 'bigInt' : 'int'](); 18 | case "null": 19 | case "undefined": 20 | return null; 21 | // Non primitives 22 | case "Date": 23 | return faker.date.anytime(); 24 | // Array primitives 25 | case "string[]": 26 | return new Array(rand(10)).fill('.').map(() => forStrings.checkStringName(name)); 27 | case "boolean[]": 28 | return new Array(rand(10)).fill('.').map(b => !!rand(2, 0)); 29 | case "number[]": 30 | case "bigint[]": 31 | return new Array(rand(10)).fill('.').map(n => type === 'bigint[]' ? BigInt(rand(10, 0)) : rand(10, 0)); 32 | case 'null[]': 33 | case 'undefined[]': 34 | return new Array(rand(5, 0)).fill(null); 35 | // Array non-primitive 36 | case 'Date[]': 37 | return new Array(rand(10, 0)).fill('.').map((_) => faker.date.anytime()) 38 | case 'any': 39 | case 'any[]': 40 | return anyReturn; 41 | default: 42 | return null; 43 | } 44 | } -------------------------------------------------------------------------------- /static/butler.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 10 | 11 | 12 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /core/utils/typeValidation.ts: -------------------------------------------------------------------------------- 1 | import { validTypes } from "@core/constants/ValidTypes.ts"; 2 | import { rand } from "@core/utils/randNumber.ts"; 3 | 4 | export const typeValidation = (type: string): { type: string, isNotCustom: boolean, value: string | number | boolean | null} => { 5 | if(type.includes("|")) { 6 | const splitTypes = type.split("|"), 7 | findPossibleValues = splitTypes.filter(t => { 8 | if(t.includes("'") || t.includes('"')) return true; 9 | if(!isNaN(Number(t))) return true; 10 | return false; 11 | }); 12 | 13 | if(findPossibleValues.length > 0) { 14 | const randomValue = findPossibleValues[rand(findPossibleValues.length, 0)]; 15 | 16 | const isNumber = !(randomValue.includes('"') || randomValue.includes("'")) && !isNaN(randomValue as unknown as number); 17 | 18 | return { type: typeof randomValue, isNotCustom: true, value: isNumber ? Number(randomValue) : randomValue.replace(/('|")/g, '').trim()}; 19 | } else { 20 | const types = splitTypes.filter(t => validTypes.includes(t)); 21 | 22 | const fixedType = types[rand(types.length)] 23 | 24 | return { type: fixedType, isNotCustom: validTypes.includes(fixedType), value: null} 25 | } 26 | 27 | } 28 | 29 | if(type.includes("'") || type.includes('"')) { 30 | return { type: typeof type, isNotCustom: true, value: type.replace(/('|")/g, '')}; 31 | } 32 | 33 | if(!isNaN(Number(type))) { 34 | return { type: typeof type, isNotCustom: true, value: Number(type)}; 35 | } 36 | 37 | if(type === 'true' || type === 'false') { 38 | return { type: 'boolean', isNotCustom: true, value: JSON.parse(type)}; 39 | } 40 | 41 | return {type, isNotCustom: validTypes.includes(type), value: null}; 42 | } -------------------------------------------------------------------------------- /core/utils/strings.ts: -------------------------------------------------------------------------------- 1 | import { Faker } from "https://esm.sh/@faker-js/faker@8.0.2"; 2 | 3 | export default class StringPlaceholder { 4 | 5 | constructor(private faker: Faker) {} 6 | 7 | checkStringName(name: string) { 8 | if(name.toLowerCase().includes('id')) return this.faker.string.uuid(); 9 | if(name.toLowerCase().includes('avatar')) return this.faker.internet.avatar(); 10 | if(name.toLowerCase().includes('url')) return this.faker.internet.url(); 11 | if(name.toLowerCase().includes('image') || name.toLocaleLowerCase().includes('img') || name.toLocaleLowerCase().includes('thumbnail')) return this.faker.image.url(); 12 | if(name.toLowerCase().includes('email')) return this.faker.internet.email(); 13 | return this.checkReservedString(name); 14 | } 15 | 16 | checkReservedString(name: string) { 17 | const fullName = this.faker.person.fullName(), 18 | firstName = this.faker.person.firstName(), 19 | middleName = this.faker.person.middleName(), 20 | lastName = this.faker.person.lastName(), 21 | address = this.faker.location.streetAddress(); 22 | 23 | const reservedString = { 24 | name: fullName, 25 | full_name: fullName, 26 | fullName: fullName, 27 | firstName: firstName, 28 | first_name: firstName, 29 | middleName: middleName, 30 | middle_name: middleName, 31 | lastName: lastName, 32 | last_name: lastName, 33 | homepage: this.faker.internet.url(), 34 | address: address, 35 | first_address: address, 36 | primaryAddress: address, 37 | firstAddress: address, 38 | mail: this.faker.internet.email(), 39 | location: this.faker.location.country(), 40 | bio: this.faker.lorem.paragraph(), 41 | description: this.faker.lorem.paragraph() 42 | }[name] 43 | 44 | return reservedString || this.faker.lorem.slug({ min: 1, max: 5}); 45 | } 46 | 47 | 48 | 49 | } -------------------------------------------------------------------------------- /static/fonts/poppins.css: -------------------------------------------------------------------------------- 1 | /* devanagari */ 2 | @font-face { 3 | font-family: 'Poppins'; 4 | font-style: normal; 5 | font-weight: 300; 6 | font-display: swap; 7 | src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLDz8Z11lFd2JQEl8qw.woff2) format('woff2'); 8 | unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FF; 9 | } 10 | /* latin-ext */ 11 | @font-face { 12 | font-family: 'Poppins'; 13 | font-style: normal; 14 | font-weight: 300; 15 | font-display: swap; 16 | src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLDz8Z1JlFd2JQEl8qw.woff2) format('woff2'); 17 | unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 18 | } 19 | /* latin */ 20 | @font-face { 21 | font-family: 'Poppins'; 22 | font-style: normal; 23 | font-weight: 300; 24 | font-display: swap; 25 | src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLDz8Z1xlFd2JQEk.woff2) format('woff2'); 26 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 27 | } 28 | /* devanagari */ 29 | @font-face { 30 | font-family: 'Poppins'; 31 | font-style: normal; 32 | font-weight: 700; 33 | font-display: swap; 34 | src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLCz7Z11lFd2JQEl8qw.woff2) format('woff2'); 35 | unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FF; 36 | } 37 | /* latin-ext */ 38 | @font-face { 39 | font-family: 'Poppins'; 40 | font-style: normal; 41 | font-weight: 700; 42 | font-display: swap; 43 | src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLCz7Z1JlFd2JQEl8qw.woff2) format('woff2'); 44 | unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 45 | } 46 | /* latin */ 47 | @font-face { 48 | font-family: 'Poppins'; 49 | font-style: normal; 50 | font-weight: 700; 51 | font-display: swap; 52 | src: url(https://fonts.gstatic.com/s/poppins/v20/pxiByp8kv8JHgFVrLCz7Z1xlFd2JQEk.woff2) format('woff2'); 53 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 54 | } -------------------------------------------------------------------------------- /static/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Head } from "$fresh/runtime.ts"; 2 | import { Handlers } from "$fresh/server.ts"; 3 | import EditorView from "../islands/EditorView.tsx"; 4 | 5 | export const handler: Handlers = { 6 | GET: (req, ctx) => { 7 | if(req.url.includes('butlermock.deno.dev')) { 8 | return new Response('', { status: 301, headers: { location: 'https://butlermock.online' }}); 9 | } 10 | return ctx.render(); 11 | } 12 | } 13 | 14 | export default function Home() { 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Butlermock 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 39 |
40 |

45 | Butlermock 46 |

47 | Just type that mock! 48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | Github logo 56 | 57 |
58 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Butlermock 2 | 3 | [![Made with Fresh](https://fresh.deno.dev/fresh-badge-dark.svg)](https://fresh.deno.dev) 4 | 5 | Buy Me A Coffee 6 | 7 | Tool for building mocks from typescript's types/interfaces into object 8 | with actual data using Fakerjs 9 | 10 | > Documentation on develop, in the near future I will expose the endpoints for you to use it within your client application, so, stay on tune 🐒 11 | 12 | Table of contents 13 | ================= 14 | 15 | 16 | * [Web usage](#web-usage) 17 | * [API usage](#api-documentation) 18 | * [Example as package/library](#Example-as-package/library) 19 | * [FAQs](#faqs) 20 | * [Limitations](#limitations) 21 | * [Status](#status) 22 | 23 | 24 | # Web usage 25 | 26 | Just go to https://butlermock.online/ and in the left panel paste your interfaces or types, just be aware of the [current limitations](#status) ( I am working for supporting the others ) 27 | 28 | Wait until the monaco editor shows up and paste your interfaces to later clicking the play button and the mock shows up almost immediately. 29 | 30 | ![Butlermock's landing](doc/assets/landing.png) 31 | 32 | > ![button guide](doc/assets/buttons.png) 33 | 34 | > The "play" button mocks up, "clipboard" copies the mock created and the "X" button is for cleaning the view 35 | 36 | # API documentation 37 | 38 | Easy peasy 39 | 40 | ``` typescript 41 | 42 | fetch(new URL("/api/mock", 'https://butlermock.online').href, { 43 | method: "POST", 44 | headers: { 45 | "content-type": "application/json", 46 | }, 47 | body: JSON.stringify({ 48 | value: `interface Test { 49 | Hello: "World!"; 50 | }`, 51 | valueForAny: "null", // any parseable json value, values: "null" | "\"Whatever the text you want\"" | "{}" | "0" | "1" | ... | "9" 52 | mustReturn: "", // empty or the top-level name of the interfaces that you provided 53 | quantity: 1, // if is greater than 1, the api response will be an array of the interfaces provided 54 | }), 55 | }) 56 | .then(res => res.json()).then(console.log); // should log: 57 | //{ 58 | // "Test": { 59 | // "Hello": "World!" 60 | // } 61 | //} 62 | 63 | ``` 64 | 65 | > **Always remember to format your interfacer or type correctly, if you forget to add ";" it may result in bad parsing** ![Bad format](doc/assets/Butlemock-bad-formatting.jpeg); 66 | 67 | 68 | ### Recommedations when using the API 69 | 70 | If you are going to consume the API I suggest that you use fetch() instead of your own HTTPS calling implementation, to avoid sending credentials which I don't store, but security reasons try to use it in the way I suggest, just for privacy reasons. **REPEATING, I DO NOT STORE ANY DATA. :)**. 71 | 72 | 73 | # Example as package/library 👷🏻‍♂️🚧 74 | 75 | [Check here](https://github.com/Devrax/Butlermock-package) > Under construction 🚧 76 | 77 | # Guide when providing an interface or type to be mocked up 78 | 79 | ```typescript 80 | const mock = new Interface2Mock(`interface Greeting { 81 | hello: string; 82 | cursed: { 83 | damn: string; 84 | }[]; 85 | }`); // ❌ Butler mock cannot process direct objects, yet 86 | ``` 87 | 88 | ```typescript 89 | const mock = new Interface2Mock(`interface Greeting { 90 | hello: string; 91 | cursed: CursedWord[]; 92 | } 93 | 94 | type CursedWord = { 95 | damn: string; 96 | }`); // ✅ Butler Mock can format this way 97 | ``` 98 | 99 | # FAQs 100 | 101 | ### **Q: Is it using an AI?** 102 | A: No :) 103 | 104 | ### Q: How can I tell the API or website to give me a fixed value? 105 | A: You just type explicitly the value you want: 106 | ``` typescript 107 | interface ThisIsSparta { 108 | troops: 300; 109 | leader: "Leonidas"; 110 | murderedBy: true; 111 | } 112 | 113 | interface mightBeSparta { 114 | troops: 200 | 594 | 2893 | 39; 115 | leader: "Trump" | "Me" | "You?"; 116 | murderedBy: true; // fixed Booleans can not be multiples values, for that use explicitly 'boolean' 117 | } 118 | ``` 119 | 120 | ### Q: Can I copy full code snippet and it will detect the interfaces? 121 | A: Yes :) 122 | ![Can I copy full code snippet and it will detect the interfaces?](doc/assets/IlustrationQuestion.png) 123 | 124 | # Limitations 125 | 126 | This section is for known limitations that Butlermocks has, but in the future might be able to recognize and mock: 127 | 128 | |type| casting | description | 129 | |----|---------|-------------| 130 | |Array| Array| This casting is not supported, use (string \| boolean \| number \| any)[] instead| 131 | |fixed string | When fixed string includes ";" or ":" | When providing a fixed string value with a semicolon in it, you must use escape for that semicolon inside the string for correct json representation; Example: `interface Example { fixedString: "I am using a semicolon '\\;' so I used escaping '\\' :)"; }`| 132 | 133 | # Status 134 | 135 | - [x] *Interfaces* 136 | ```typescript 137 | interface NonMockeableInterface {} // ❌ empty interface 138 | 139 | interface MockeableInterface { 140 | somethingToMock: any; 141 | } // ✅ Mockeable 142 | 143 | export interface AnotherMockeableInterface { 144 | anotherThingToMock: any; 145 | } // ✅ Mockeable, 'export' is ignored 146 | ``` 147 | 148 |
149 | 150 | - [x] Interfaces with nested interfaces/types 151 | ```typescript 152 | type mockeableType = { 153 | name: string; 154 | } // ✅ Mockeable 155 | 156 | interface MockeableInterface { 157 | somethingToMock: any; 158 | nestedInterface: AnotherMockeableInterface; 159 | extraNestedInterface: AnotherMockeableInterface[]; 160 | } // ✅ Mockeable 161 | 162 | export interface AnotherMockeableInterface { 163 | anotherThingToMock: any; 164 | } // ✅ Mockeable, 'export' is ignored 165 | ``` 166 | 167 |
168 | 169 | 170 | - [x] Interfaces that extends from other interfaces/types 171 | ``` typescript 172 | interface That { ... } 173 | 174 | interface This extends That {} // ✅ Mockeable 175 | ``` 176 | 177 |
178 | 179 | - [ ] Generic Interfaces 180 | ``` typescript 181 | // Not mockeable yet 182 | interface This { 183 | here: T; 184 | } // ❌ 185 | ``` 186 | 187 |
188 | 189 | - [ ] Generic Interfaces that extends from other interfaces/types 190 | ```Typescript 191 | // Not mockeable yet 192 | interface Evnt { 193 | name: T; 194 | } 195 | 196 | interface IPropertiesToAdd extends Evnt { 197 | on(): void; 198 | off(): void; 199 | } // ❌ 200 | ``` 201 | 202 |
203 | 204 | - [x] Primitive Type 205 | ``` typescript 206 | // Not mockeable yet 207 | type justString = string; // ✅ Mockeable 208 | ``` 209 | 210 |
211 | 212 | - [x] Type object 213 | ``` typescript 214 | type justString = { 215 | yes: 'you can'; 216 | }; // ✅ Mockeable 217 | ``` 218 | 219 |
220 | 221 | - [x] Type with nested interfaces/types 222 | ``` typescript 223 | 224 | type mockeableType = { 225 | nestedObject: MockeableInterface; 226 | } // ✅ Mockeable 227 | 228 | interface MockeableInterface { 229 | somethingToMock: any; 230 | } // ✅ Mockeable 231 | ``` 232 | 233 |
234 | 235 | - [ ] Generic Type 236 | ``` typescript 237 | // Not mockeable yet 238 | type IPropertiesToAdd = T & { 239 | on(): void 240 | off(): void 241 | }; // ❌ 242 | ``` 243 | -------------------------------------------------------------------------------- /core/class/TypeMocker.ts: -------------------------------------------------------------------------------- 1 | import { checkConstant } from "@core/utils/dynamicValue.ts"; 2 | import { rand } from "@core/utils/randNumber.ts"; 3 | import { typeValidation } from "@core/utils/typeValidation.ts"; 4 | 5 | type Primitives = string | boolean | number; 6 | interface KeyValueObject { [k: string]: T }; 7 | interface TsObject { raw: string, value: KeyValueObject | Primitives; isProcess: boolean }; 8 | 9 | export default class Interface2Mock { 10 | 11 | #json: KeyValueObject = {}; 12 | #interfacePatternRegex = /interface(([A-Za-z0-9 ]+)({)(.+)?)(})/g; 13 | #interfaceExtendsPatternRegex = /interface(([A-Za-z0-9 ]+)extends([A-Za-z0-9 ]+)({)(.+)?)(})/g 14 | #typePatternRegex = /type(([A-Za-z0-9 ]+)= ?)({)(.+)?(})/g; 15 | #typePrimitivePatternRegex = /type(([A-Za-z0-9 ]+)= ?)(.+);/g; 16 | 17 | #interfacesCaptured: KeyValueObject> = {}; 18 | #typeCaptured: KeyValueObject> = {}; 19 | 20 | constructor(private interfaceReference: string, private anyReturn: unknown = null) { 21 | this.interfaceReference = this.interfaceReference.replace(/\\;/g, "#SEMICOLON#").replace(/\\:/g, "#TWO_POINTS#"); 22 | this.#capturePrimitiveTypes(this.interfaceReference); 23 | this.#captureStandardTypesAndInterfaces(this.interfaceReference); 24 | 25 | if(Object.values(this.#interfacesCaptured).length === 0 && Object.values(this.#typeCaptured).length === 0) throw new Error('No interfaces or types were found'); 26 | 27 | [...Object.values(this.#interfacesCaptured), ...Object.values(this.#typeCaptured)] 28 | .filter(el => Boolean(el) && !el.isProcess) 29 | .forEach(obj => this.#process(obj)); 30 | 31 | this.#captureExtendedInterfaces(this.interfaceReference); 32 | } 33 | 34 | #captureExtendedInterfaces(str: string) { 35 | str = str.replace(/{ +/g, '{').replace(/{\n+/g, '{').replace(/\n( +)/g, '').replace(/}/g, '}\n').replace(/;\n/g, ';'); //Remove any whitespace after line break 36 | const interfacesExtends = str.match(this.#interfaceExtendsPatternRegex); 37 | if(interfacesExtends == null) return; 38 | for(let interfaceExtend of interfacesExtends) { 39 | interfaceExtend = interfaceExtend.replace(/\n+/g, ' '); 40 | const processItem = (/interface(([A-Za-z0-9 ]+)extends([A-Za-z0-9 ]+)({)(.+)?)(})/g).exec(interfaceExtend)!; 41 | if (processItem == null) throw new Error('Bad format for interface/type: ' + interfaceExtend); 42 | const [useless1, useless2, keyObject, keyExtends, ...uselessRest] = processItem; 43 | const existKeyExtends = (this.#interfacesCaptured[keyExtends.trim()]?.value as {}) ?? {}; 44 | this.#interfacesCaptured[keyObject.trim()].value = { 45 | ...existKeyExtends, 46 | ...this.#interfacesCaptured[keyObject.trim()].value as {}, 47 | } 48 | 49 | } 50 | } 51 | 52 | 53 | #capturePrimitiveTypes(str: string) { 54 | const primitiveTypes = str.match(this.#typePrimitivePatternRegex); 55 | if(primitiveTypes == null) return; 56 | for(let primitiveStr of primitiveTypes) { 57 | primitiveStr = primitiveStr.replace(/\n+/g, ' '); 58 | const processItem = (/type(([A-Za-z0-9 ]+)= ?)([A-Za-z0-9 |"\n]+);/g).exec(primitiveStr)!; 59 | if (processItem == null) throw new Error('Bad format for interface/type: ' + primitiveStr); 60 | const [useless, useless2, typeName, typeValue] = processItem; 61 | 62 | const deepTypeValid = typeValidation(typeValue), 63 | value = deepTypeValid.value ?? checkConstant(typeName.trim(), deepTypeValid.type, this.anyReturn) as any; 64 | 65 | this.#typeCaptured[typeName.trim()] = { 66 | raw: typeValue, 67 | value, 68 | isProcess: true 69 | } 70 | 71 | } 72 | 73 | } 74 | 75 | #captureStandardTypesAndInterfaces(str: string) { 76 | str = str.replace(/{ +/g, '{').replace(/{\n+/g, '{').replace(/\n( +)/g, '').replace(/}/g, '}\n').replace(/;\n/g, ';'); //Remove any whitespace after line break 77 | const interfacesTaken = str.match(this.#interfacePatternRegex); 78 | const typesTaken = str.match(this.#typePatternRegex); 79 | 80 | const reusableIterator = (arr: string[], pattern: string, storeRef: KeyValueObject>) => { 81 | for (const item of arr) { 82 | const processItem = new RegExp(pattern, 'g').exec(item.replace(/extends([A-Za-z0-9 ]+)/, '')); 83 | if (processItem == null) throw new Error('Bad format for interface/type: ' + item); 84 | const [useless1, useless2, hashkey, openBracket, tsObject, closingBracket] = processItem; 85 | if((tsObject == null || tsObject === '') && !item.includes('extends')) throw new Error(`"${item}" it seems empty, you cannot provide empty interface.`); 86 | storeRef[hashkey.trim()] = { 87 | raw: `${openBracket} ${tsObject ?? ''} ${closingBracket}`.trim(), 88 | value: {}, 89 | isProcess: false 90 | } 91 | 92 | } 93 | } 94 | 95 | if (interfacesTaken) reusableIterator(interfacesTaken, 'interface(([A-Za-z0-9 ]+)({)(.+)?)(})', this.#interfacesCaptured); 96 | if (typesTaken) reusableIterator(typesTaken, 'type(([A-Za-z0-9 ]+)= ?)({)(.+)?(})', this.#typeCaptured); 97 | } 98 | 99 | /** 100 | * This function process/map each interface or types and creates the mock object assigning each value meeting its type one by one 101 | * is recursive 102 | * @param obj 103 | * @returns 104 | */ 105 | #process(obj: TsObject) { 106 | if (obj == null) return null; 107 | const splitEachMember = obj.raw.replace(/(\n|\t|{|}|readonly )/g, '').trim().split(';').filter(Boolean); 108 | for (const member of splitEachMember) { 109 | 110 | if(member.match(/\[(.+)\]/g) || member.match(/\((.+)\)/g)) break; 111 | 112 | let [keyName, ...typeValue] = member.replace('#SEMICOLON#', ';').split(':'); 113 | const type = typeValue == null ? (`${typeValue}`).toLocaleLowerCase() : typeValue.map(v => v.replace("#TWO_POINTS#", ':')).join('').trim(); 114 | keyName = keyName.replace('?', ''); 115 | const deepTypeValid = typeValidation(type); 116 | if (deepTypeValid.isNotCustom) { 117 | (obj.value as KeyValueObject)[keyName] = deepTypeValid.value ?? checkConstant(keyName, deepTypeValid.type, this.anyReturn); 118 | } else { 119 | const hashtype = String(deepTypeValid.type).replace(/\[\]/, ''); 120 | const checkIfThatInterfaceExist = this.#findTypeValue(hashtype); 121 | if (checkIfThatInterfaceExist) { 122 | const foundInterface = this.#interfacesCaptured[hashtype] ?? this.#typeCaptured[hashtype]; 123 | const recursiveValue = (t: string) => { 124 | const val = this.#process(foundInterface); 125 | return t.includes('[]') ? new Array(rand(5)).fill(structuredClone(val)) : val 126 | } 127 | const value = checkIfThatInterfaceExist?.isProcess ? checkIfThatInterfaceExist.value : recursiveValue(deepTypeValid.type); 128 | (obj.value as KeyValueObject)[keyName] = deepTypeValid.type.includes('[]') ? Array.isArray(value) ? value : [value] : value; 129 | } else { 130 | (obj.value as KeyValueObject)[keyName] = null; 131 | } 132 | } 133 | } 134 | obj.isProcess = true; 135 | return structuredClone(obj.value); 136 | } 137 | 138 | #findTypeValue(type: string) { 139 | const checkInterfaces = this.#interfacesCaptured[type]; 140 | const checkTypes = this.#typeCaptured[type]; 141 | 142 | if (checkInterfaces) return checkInterfaces; 143 | if (checkTypes) return checkTypes 144 | } 145 | 146 | public buildMock(rootTypeInterface = '') { 147 | if (rootTypeInterface) { 148 | if (this.#interfacesCaptured[rootTypeInterface] == null && this.#typeCaptured[rootTypeInterface] == null) { 149 | throw new Error(`Not matches for key '${rootTypeInterface}' in [${Object.keys({...this.#interfacesCaptured, ...this.#typeCaptured}).join(', ')}]`); 150 | } 151 | 152 | const selectedInterface = this.#interfacesCaptured[rootTypeInterface]; 153 | return selectedInterface ? selectedInterface.value : this.#typeCaptured[rootTypeInterface].value; 154 | } 155 | 156 | const iterateMockedInterface = (tsObj: KeyValueObject>) => Object.entries(tsObj).forEach(obj => this.#json[obj[0]] = obj[1].value); 157 | iterateMockedInterface(this.#interfacesCaptured); 158 | iterateMockedInterface(this.#typeCaptured); 159 | return structuredClone(this.#json); 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /islands/EditorView.tsx: -------------------------------------------------------------------------------- 1 | import { useSignal } from "@preact/signals"; 2 | import { useEffect, useRef } from "preact/hooks"; 3 | import EditorResponse from "../components/EditorResponse.tsx"; 4 | 5 | declare var theEditor: any; 6 | 7 | declare var previewer: any; 8 | 9 | const fetchTransformation = async ( 10 | value: string, 11 | valueForAny: string, 12 | mustReturn = "", 13 | quantity = 1, 14 | ) => { 15 | const json = await fetch(new URL("/api/typemocker", location.origin).href, { 16 | method: "POST", 17 | headers: { 18 | "content-type": "application/json", 19 | }, 20 | body: JSON.stringify({ 21 | value, 22 | valueForAny, 23 | mustReturn, 24 | quantity, 25 | }), 26 | }); 27 | return JSON.stringify(await json.json(), null, 2); 28 | }; 29 | 30 | export default function EditorView() { 31 | const dataList = useSignal([]); 32 | const inputRef = useRef(null); 33 | const inputNumberRef = useRef(null); 34 | const isLoading = useSignal(false); 35 | const codeToShow = useSignal(""); 36 | const valueForAny = useSignal("null"); 37 | const valuesForAny = ["null", "{}", "0", '"lorem ipsum"']; 38 | 39 | const fetchAndShow = async () => { 40 | try { 41 | isLoading.value = true; 42 | const code = await fetchTransformation( 43 | theEditor.getValue(), 44 | valueForAny.value, 45 | inputRef.current!.value, 46 | inputNumberRef.current!.valueAsNumber, 47 | ); 48 | codeToShow.value = code; 49 | } finally { 50 | isLoading.value = false; 51 | } 52 | }; 53 | 54 | const fetchAndCopy = async () => { 55 | navigator.clipboard.writeText(previewer.getValue()); 56 | }; 57 | 58 | const cleanEditors = () => { 59 | inputRef.current!.value = ""; 60 | inputNumberRef.current!.value = ""; 61 | theEditor.setValue(""); 62 | previewer.setValue(""); 63 | }; 64 | 65 | function checkInterfacesAndTypesName() { 66 | const regex = () => /(interface([0-9A-Za-z ]+){|type([0-9A-Za-z ]+)=)/g; 67 | const searchTypesAndInterface = (theEditor.getValue() as string).match( 68 | regex(), 69 | ); 70 | 71 | dataList.value = searchTypesAndInterface === null 72 | ? [] 73 | : searchTypesAndInterface 74 | .map((m: string) => { 75 | const result = regex().exec(m); 76 | return ((result && (result[2] || result[3])?.trim()) || "").replace(/extends([A-Za-z0-9 ]+)/, ''); 77 | }) 78 | .filter(Boolean); 79 | } 80 | 81 | useEffect(() => { 82 | setTimeout(() => { 83 | theEditor?.onDidPaste(() => fetchAndShow()); 84 | theEditor?.onDidBlurEditorText(() => checkInterfacesAndTypesName()); 85 | inputRef.current!.onfocus = checkInterfacesAndTypesName; 86 | }, 500); 87 | }, []); 88 | 89 | return ( 90 | <> 91 |
92 |
93 |
94 | 95 | 110 |
111 |
112 | 164 | 185 | 206 |
207 | 208 |
209 | 217 | 218 | {dataList.value.map((n) => ( 219 | 222 |
223 | 224 |
225 | 232 |
233 |
234 |
235 |
236 |
237 |
238 | butler loader 239 |
240 | 241 |
242 | 243 | 245 | 246 | 247 | ); 248 | } 249 | -------------------------------------------------------------------------------- /core/class/TypeMocker.test.ts: -------------------------------------------------------------------------------- 1 | import { assertThrows } from "https://deno.land/std@0.198.0/assert/mod.ts"; 2 | import Interface2Mock from "@core/class/TypeMocker.ts"; 3 | import { assertEquals } from "https://deno.land/std@0.198.0/assert/assert_equals.ts"; 4 | import { assert } from "https://deno.land/std@0.140.0/_util/assert.ts"; 5 | import { assertExists } from 'https://deno.land/std@0.198.0/assert/assert_exists.ts'; 6 | 7 | Deno.test("Providing no interface", () => { 8 | assertThrows(() => new Interface2Mock(''), Error, 'No interfaces or types were found'); 9 | }); 10 | 11 | Deno.test("Providing single interface", () => { 12 | const mock = new Interface2Mock(`interface Greeting { 13 | hello: string; 14 | }`); 15 | 16 | const objMocked = mock.buildMock(); 17 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: { hello: 'world' }})); 18 | 19 | const specificObjMocked = mock.buildMock('Greeting'); 20 | assertEquals(Object.keys(specificObjMocked), Object.keys({ hello: 'world' })); 21 | 22 | const noSpecificObjMocked = mock.buildMock(''); 23 | assertEquals(Object.keys(noSpecificObjMocked), Object.keys({ Greeting: { hello: 'world' }})); 24 | }); 25 | 26 | Deno.test("Providing two interfaces", () => { 27 | const mock = new Interface2Mock(` 28 | interface Greeting { 29 | hello: string; 30 | } 31 | 32 | interface CursedWord { 33 | damn: string; 34 | } 35 | `); 36 | 37 | const objMocked = mock.buildMock(); 38 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: null, CursedWord: null })); 39 | 40 | const specificObjGreetingMocked = mock.buildMock('Greeting'); 41 | assertEquals(Object.keys(specificObjGreetingMocked), Object.keys({ hello: 'world' })); 42 | 43 | const specificObjCursedMocked = mock.buildMock('CursedWord'); 44 | assertEquals(Object.keys(specificObjCursedMocked), Object.keys({ damn: 'f*ck' })); 45 | 46 | const noSpecificObjMocked = mock.buildMock(''); 47 | assertEquals(Object.keys(noSpecificObjMocked), Object.keys({ Greeting: null, CursedWord: null})); 48 | }); 49 | 50 | Deno.test("Providing two interfaces - 2", () => { 51 | const mock = new Interface2Mock(`interface FoundationContact { 52 | type: 'tel' | string; 53 | displayValue: string; 54 | value: string; 55 | urlScheme: 'tel'; 56 | } 57 | 58 | export interface FoundationInformation { 59 | id: string; 60 | title: string; 61 | hasRNC?: boolean; 62 | contacts: FoundationContact[]; 63 | receivings: string[]; 64 | description: string; 65 | location: string; 66 | }`); 67 | 68 | const objMocked = mock.buildMock(); 69 | assertEquals(Object.keys(objMocked), Object.keys({ FoundationContact: null, FoundationInformation: null })); 70 | assertExists(mock.buildMock('FoundationContact')); 71 | assertExists(mock.buildMock('FoundationInformation')); 72 | assert(Array.isArray(mock.buildMock('FoundationInformation').contacts)); 73 | assertEquals(mock.buildMock('FoundationContact').urlScheme, 'tel'); 74 | }); 75 | 76 | Deno.test("Providing three interfaces with one duplication", () => { 77 | const mock = new Interface2Mock(`interface FoundationContact { 78 | type: 'tel' | string; 79 | displayValue: string; 80 | value: string; 81 | urlScheme: 'tel'; 82 | } 83 | 84 | export interface FoundationInformation { 85 | id: string; 86 | title: string; 87 | hasRNC?: boolean; 88 | contacts: FoundationContact[]; 89 | receivings: string[]; 90 | description: string; 91 | location: string; 92 | } 93 | 94 | export interface FoundationInformation { 95 | id: string; 96 | title: string; 97 | hasRNC?: boolean; 98 | contacts: FoundationContact[]; 99 | receivings: string[]; 100 | description: string; 101 | location: string; 102 | }`); 103 | 104 | const objMocked = mock.buildMock(); 105 | assertEquals(Object.keys(objMocked), Object.keys({ FoundationContact: null, FoundationInformation: null })); 106 | }); 107 | 108 | Deno.test("Providing nested Interface", () => { 109 | const mock = new Interface2Mock(` 110 | interface Greeting { 111 | hello: string; 112 | cursed: CursedWord; 113 | } 114 | 115 | interface CursedWord { 116 | damn: string; 117 | } 118 | `); 119 | 120 | const objMocked = mock.buildMock(); 121 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: null, CursedWord: null })); 122 | 123 | const specificObjGreetingMocked = mock.buildMock('Greeting') as unknown as { hello: string, cursed: { damn: string}}; 124 | assertEquals(Object.keys(specificObjGreetingMocked), Object.keys({ hello: null, cursed: null })); 125 | assertEquals(Object.keys(specificObjGreetingMocked.cursed), Object.keys({ damn: null })); 126 | }); 127 | 128 | Deno.test("Providing nested unknown Interface", () => { 129 | const mock = new Interface2Mock(` 130 | interface Greeting { 131 | hello: string; 132 | cursed: CursedWord; 133 | }`); 134 | 135 | const objMocked = mock.buildMock() as unknown as { Greeting: { hello: string, cursed: string } }; 136 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: { hello: null, cursed: null } })); 137 | assertEquals(objMocked.Greeting.cursed, null); 138 | }); 139 | 140 | Deno.test("Providing single Type", () => { 141 | const mock = new Interface2Mock(`type Greeting = { 142 | hello: string; 143 | }`); 144 | 145 | const objMocked = mock.buildMock(); 146 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: { hello: 'world' }})); 147 | 148 | const specificObjMocked = mock.buildMock('Greeting'); 149 | assertEquals(Object.keys(specificObjMocked), Object.keys({ hello: 'world' })); 150 | 151 | const noSpecificObjMocked = mock.buildMock(''); 152 | assertEquals(Object.keys(noSpecificObjMocked), Object.keys({ Greeting: { hello: 'world' }})); 153 | }); 154 | 155 | Deno.test("Providing two Type", () => { 156 | const mock = new Interface2Mock(`type Greeting = { 157 | hello: string; 158 | } 159 | 160 | type CursedWord = { 161 | damn: string; 162 | }`); 163 | 164 | const objMocked = mock.buildMock(); 165 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: null, CursedWord: null })); 166 | 167 | const specificObjGreetingMocked = mock.buildMock('Greeting'); 168 | assertEquals(Object.keys(specificObjGreetingMocked), Object.keys({ hello: 'world' })); 169 | 170 | const specificObjCursedMocked = mock.buildMock('CursedWord'); 171 | assertEquals(Object.keys(specificObjCursedMocked), Object.keys({ damn: 'f*ck' })); 172 | 173 | const noSpecificObjMocked = mock.buildMock(''); 174 | assertEquals(Object.keys(noSpecificObjMocked), Object.keys({ Greeting: null, CursedWord: null})); 175 | }); 176 | 177 | Deno.test("Providing nested Type", () => { 178 | const mock = new Interface2Mock(`type Greeting = { 179 | hello: string; 180 | cursed: CursedWord; 181 | } 182 | 183 | type CursedWord = { 184 | damn: string; 185 | }`); 186 | 187 | const objMocked = mock.buildMock(); 188 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: null, CursedWord: null })); 189 | 190 | const specificObjGreetingMocked = mock.buildMock('Greeting') as unknown as { hello: string, cursed: { damn: string}}; 191 | assertEquals(Object.keys(specificObjGreetingMocked), Object.keys({ hello: null, cursed: null })); 192 | assertEquals(Object.keys(specificObjGreetingMocked.cursed), Object.keys({ damn: null })); 193 | }); 194 | 195 | Deno.test("Providing nested unknown Type", () => { 196 | const mock = new Interface2Mock(`type Greeting = { 197 | hello: string; 198 | cursed: CursedWord; 199 | }`); 200 | 201 | const objMocked = mock.buildMock() as unknown as { Greeting: { hello: string, cursed: string } }; 202 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: { hello: null, cursed: null } })); 203 | assertEquals(objMocked.Greeting.cursed, null); 204 | }); 205 | 206 | Deno.test("Providing a type and an interface", () => { 207 | const mock = new Interface2Mock(`type Greeting = { 208 | hello: string; 209 | } 210 | 211 | interface CursedWord { 212 | damn: string; 213 | } 214 | `); 215 | 216 | const objMocked = mock.buildMock() as unknown as { Greeting: { hello: string }, CursedWord: { damn: string} }; 217 | assertEquals(Object.keys(objMocked), Object.keys({ CursedWord: null, Greeting: null })); 218 | assertEquals(Object.keys(objMocked.Greeting), Object.keys({ hello: 'hi' })); 219 | assertEquals(Object.keys(objMocked.CursedWord), Object.keys({ damn: 'f*ck' })); 220 | }); 221 | 222 | Deno.test("Providing a nested type in an interface", () => { 223 | const mock = new Interface2Mock(`interface Greeting { 224 | hello: string; 225 | cursed: CursedWord; 226 | } 227 | 228 | type CursedWord = { 229 | damn: string; 230 | } 231 | `); 232 | 233 | const objMocked = mock.buildMock(); 234 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: null, CursedWord: null })); 235 | 236 | const specificObjGreetingMocked = mock.buildMock('Greeting') as unknown as { hello: string, cursed: { damn: string}}; 237 | assertEquals(Object.keys(specificObjGreetingMocked), Object.keys({ hello: null, cursed: null })); 238 | assertEquals(Object.keys(specificObjGreetingMocked.cursed), Object.keys({ damn: null })); 239 | }); 240 | 241 | Deno.test("Providing a nested array type in an interface", () => { 242 | const mock = new Interface2Mock(`interface Greeting { 243 | hello: string; 244 | cursed: CursedWord[]; 245 | } 246 | 247 | type CursedWord = { 248 | damn: string; 249 | } 250 | `); 251 | 252 | const objMocked = mock.buildMock('Greeting') as unknown as { hello: string, cursed: { damn: string}[]}; 253 | assert(objMocked.cursed.length >= 0); 254 | }); 255 | 256 | // Test Primitives Values 257 | 258 | Deno.test("Providing primitives string random value", () => { 259 | const mock = new Interface2Mock(`type Name = string; 260 | 261 | type name = string;`); 262 | 263 | const objMocked = mock.buildMock() as unknown as { name: string, Name: string}; 264 | assert(objMocked.name); 265 | assert(objMocked.Name); 266 | }); 267 | 268 | Deno.test("Providing primitives random string value and a fixed value", () => { 269 | const mock = new Interface2Mock(`type Name = string; 270 | 271 | type name = "Fred";`); 272 | 273 | const objMocked = mock.buildMock() as unknown as { name: string, Name: string}; 274 | assert(objMocked.name === 'Fred'); 275 | assert(objMocked.Name); 276 | }); 277 | 278 | Deno.test("Providing primitives random string value along a type with non-primitive", () => { 279 | const mock = new Interface2Mock(`type Name = string; 280 | 281 | type Person = { 282 | name: string; 283 | };`); 284 | 285 | const objMocked = mock.buildMock() as unknown as { Name: string, Person: { name: string }}; 286 | assert(objMocked.Name); 287 | assert(objMocked.Person.name); 288 | }); 289 | 290 | Deno.test("Providing primitives string fixed value along a type with non-primitive", () => { 291 | const mock = new Interface2Mock(`type Name = "Fred"; 292 | 293 | type Person = { 294 | name: "Fred"; 295 | };`); 296 | 297 | const objMocked = mock.buildMock() as unknown as { Name: string, Person: { name: string }}; 298 | assert(objMocked.Name === objMocked.Person.name); 299 | }); 300 | 301 | Deno.test("Providing primitives types with fixed value nested in other Type", () => { 302 | const mock = new Interface2Mock(`type Name = "Fred"; 303 | 304 | type Person = { 305 | name: Name; 306 | url: 'https\\://example.com'; 307 | };`); 308 | 309 | const objMocked = mock.buildMock() as unknown as { Name: string; Person: { name: string; url: string; }}; 310 | assert(objMocked.Name === objMocked.Person.name); 311 | assert(objMocked.Person.url === 'https://example.com'); 312 | }); 313 | 314 | Deno.test("Providing primitives types with random string value nested in other Type", () => { 315 | const mock = new Interface2Mock(`type Name = string; 316 | 317 | type Person = { 318 | name: Name; 319 | mail: string; 320 | };`); 321 | 322 | const objMocked = mock.buildMock() as unknown as { Name: string, Person: { name: string }}; 323 | assert(objMocked.Person.name); 324 | }); 325 | 326 | Deno.test("Providing primitives types with random string value nested in an interface", () => { 327 | const mock = new Interface2Mock(`type Name = string; 328 | 329 | interface Person { 330 | name: Name; 331 | mail: string; 332 | };`); 333 | 334 | const objMocked = mock.buildMock() as unknown as { Name: string, Person: { name: string }}; 335 | assert(objMocked.Person.name); 336 | }); 337 | 338 | Deno.test("Providing primitive types, one with random string and other with random boolean value nested in an interface", () => { 339 | const mock = new Interface2Mock(` 340 | type IsAlive = boolean; 341 | type Name = string; 342 | 343 | interface Person { 344 | name: Name; 345 | isAlive: IsAlive; 346 | mail: string; 347 | };`); 348 | 349 | const objMocked = mock.buildMock() as unknown as { Name: string, Person: { name: string }}; 350 | assert(objMocked.Person.name); 351 | }); 352 | 353 | Deno.test("Providing one line single interface", () => { 354 | const mock = new Interface2Mock(`interface Greeting {hello: string;}`); 355 | 356 | const objMocked = mock.buildMock(); 357 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: { hello: 'world' }})); 358 | 359 | const specificObjMocked = mock.buildMock('Greeting'); 360 | assertEquals(Object.keys(specificObjMocked), Object.keys({ hello: 'world' })); 361 | 362 | const noSpecificObjMocked = mock.buildMock(''); 363 | assertEquals(Object.keys(noSpecificObjMocked), Object.keys({ Greeting: { hello: 'world' }})); 364 | }); 365 | 366 | 367 | Deno.test("Providing one line single interface with fixed value ';'", () => { 368 | const mock = new Interface2Mock(`interface Greeting {hello: "Hello World \\; \\:";}`); 369 | 370 | const objMocked = mock.buildMock(); 371 | assertEquals(Object.keys(objMocked), Object.keys({ Greeting: { hello: 'world' }})); 372 | 373 | const specificObjMocked = mock.buildMock('Greeting'); 374 | assertEquals(Object.keys(specificObjMocked), Object.keys({ hello: 'world' })); 375 | 376 | const noSpecificObjMocked = mock.buildMock(''); 377 | assertEquals(Object.keys(noSpecificObjMocked), Object.keys({ Greeting: { hello: 'world' }})); 378 | }); --------------------------------------------------------------------------------