├── 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 |

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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | >
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Butlermock
2 |
3 | [](https://fresh.deno.dev)
4 |
5 |
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 | 
31 |
32 | > 
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** ;
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 | 
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 |
222 |
223 |
224 |
225 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |

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 | });
--------------------------------------------------------------------------------