fakelab 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/lib/cli.js +2 -1
- package/lib/main.d.ts +16 -8
- package/lib/main.js +3 -1
- package/lib/public/css/style.css +45 -11
- package/lib/public/favicon.ico +0 -0
- package/lib/public/icons/logo.svg +7 -0
- package/lib/public/icons/logo24.svg +9 -0
- package/lib/public/js/fakelab.min.js +43 -22
- package/lib/views/components/code.ejs +1 -1
- package/lib/views/components/sidebar.ejs +4 -3
- package/lib/views/components/toolbar.ejs +60 -0
- package/lib/views/preview.ejs +1 -189
- package/package.json +1 -1
- package/lib/views/error.ejs +0 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Fakelab
|
|
1
|
+
#  Fakelab
|
|
2
2
|
|
|
3
3
|
⚡ A fast, easy-config mock API server for frontend developers.
|
|
4
4
|
|
|
@@ -27,7 +27,7 @@ create `fakelab.config.ts` file in the project root. and reference your app inte
|
|
|
27
27
|
import { defineConfig } from "fakelab";
|
|
28
28
|
|
|
29
29
|
export default defineConfig({
|
|
30
|
-
sourcePath: "./interfaces", // can set
|
|
30
|
+
sourcePath: "./interfaces", // can set one/multiple directory(s) or typescript file(s).
|
|
31
31
|
faker: { locale: "en" }, // optional
|
|
32
32
|
server: { pathPrefix: "api/v1", port: 8080 }, // optional
|
|
33
33
|
});
|
|
@@ -61,12 +61,34 @@ export interface User {
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
##
|
|
64
|
+
## Server Command
|
|
65
65
|
|
|
66
66
|
Run:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
+
npx fakelab serve [options]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Options
|
|
73
|
+
|
|
74
|
+
| Option | Alias | Description |
|
|
75
|
+
| ----------------------- | ----- | ----------------------------------------------------- |
|
|
76
|
+
| `--source` | `-s` | Path to the source typescript file(s) or directory(s) |
|
|
77
|
+
| `--pathPrefix <prefix>` | `-x` | Prefix for all generated API routes |
|
|
78
|
+
| `--locale <locale>` | `-l` | Locale used for fake data generation |
|
|
79
|
+
| `--port <number>` | `-p` | Port to run the server on |
|
|
80
|
+
|
|
81
|
+
### Examples
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Basic usage
|
|
69
85
|
npx fakelab serve
|
|
86
|
+
|
|
87
|
+
# Custom source and port
|
|
88
|
+
npx fakelab serve -s ./interfaces -p 4000
|
|
89
|
+
|
|
90
|
+
# Custom API prefix and locale
|
|
91
|
+
npx fakelab serve --pathPrefix /v1 --locale fr
|
|
70
92
|
```
|
|
71
93
|
|
|
72
94
|
## Related
|
package/lib/cli.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import P from'path';import {fileURLToPath}from'url';import $ from'fs-extra';import l from'zod';import {Command}from'commander';import k from'express';import M from'cors';import K from'express-ejs-layouts';import q from'http';import'ejs';import G from'qs';import {Project}from'ts-morph';import {v4}from'uuid';import u from'picocolors';import Q from'figlet';import {bundleRequire}from'bundle-require';import ee from'joycon';var a=class t{static prefix(e){let r=new Date().toLocaleTimeString();return {info:u.blue,warn:u.yellow,error:u.red,success:u.green,debug:u.magenta}[e](`[${r}] FAKELAB_${e.toUpperCase()}`)}static log(e,r,...n){let o=u.white,s=t.prefix(e);(e==="error"?console.error:e==="warn"?console.warn:console.log)(s,o(r),...n);}static info(e,...r){this.log("info",e,...r);}static warn(e,...r){this.log("warn",e,...r);}static error(e,...r){this.log("error",e,...r);}static success(e,...r){this.log("success",e,...r);}};var d=class{constructor(e){this.faker=e;}JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;boolMapping={true:true,false:false};string(e){return this.execute(e,this.faker.word.noun)}int(e){return this.execute(e,this.faker.number.int)}bool(e){return this.execute(e,this.faker.datatype.boolean)}bigInt(e){return this.execute(e,this.faker.number.bigInt)}litbool(e){return this.boolMapping[e]}async object(e,r){let n={};return await Promise.all(e.map(async o=>{let s=o.getTypeAtLocation(o.getValueDeclarationOrThrow());n[o.getName()]=await r(s,this,this.readJSDocTags(o));})),n}async union(e){let r=await Promise.all(e);return r[Math.floor(Math.random()*r.length)]}async intersection(e){let r=await Promise.all(e);return r[Math.floor(Math.random()*r.length)]}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let r=e.getJsDocTags().filter(n=>n.getName()===this.JSDOC_FAKER_FIELD);return r.length===0?[]:r.map(n=>{let[o]=n.getText();if(!o)return;let s=o.text.trim().match(this.FAKER_TAG_REGEX);if(!s)return;let[,i,c]=s,p=this.evalArgs(c);return {path:i,args:p}})}execute(e,r){if(!e)return r();let n=e.path.split("."),o=this.faker;for(let s of n)o=o[s],o||(a.error("Invalid faker module path:",e.path),process.exit(1));typeof o!="function"&&(a.error("Unresolvable faker function.",e.path),process.exit(1));try{return e.args?o(e.args):o()}catch{return a.error("Passed invalid arguments to faker function."),o()}}};var y=class{constructor(e){this.files=e;let n=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=n.flatMap(o=>o.getInterfaces());}__targets;async run(e){return await e()}normalizePath(e){return e.split(P.sep).join(P.posix.sep)}entities(){let e=this.__targets.map(r=>{let n=r.getName().toLowerCase(),o=r.getType(),s=this.normalizePath(process.cwd()),c=this.normalizePath(r.getSourceFile().getDirectoryPath()).replace(s,""),p=r.getSourceFile().getBaseName(),f=`${c}/${p}`;return [n,{type:o,filepath:f}]});return new Map(e)}async loadFaker(e,r){let{faker:n}=await import(`@faker-js/faker/locale/${r||e.locale}`);return n}};async function m(t,e,r=[],n=0){if(t.isString())return e.string(r[n]);if(t.isNumber())return e.int(r[n]);if(t.isBoolean())return e.bool(r[n]);if(t.isBigInt())return e.bigInt(r[n]);if(t.isBooleanLiteral())return e.litbool(t.getText());if(t.isLiteral())return t.getLiteralValue();if(!t.isUndefined()){if(t.isUnion()){let o=t.getUnionTypes();return await e.union(o.map((s,i)=>m(s,e,r,i)))}if(t.isIntersection()){let o=t.getIntersectionTypes();return await e.intersection(o.map((s,i)=>m(s,e,r,i)))}if(t.isArray()){let o=t.getArrayElementTypeOrThrow();return [await m(o,e,r,n)]}if(t.isObject()){let o=t.getProperties();return await e.object(o,(s,i,c)=>m(s,i,c,n))}return null}}function R({each:t}){return {resolve:async r=>await Promise.all(Array.from({length:r},(n,o)=>t(o)))}}function D(t,e){return t==="uuid"?v4():e+1}function N(t,e,r){return async n=>{let o=await m(t,e);return r.uid&&typeof o=="object"?{[r.uid]:D(r.strategy,n),...o}:o}}async function L(t,e){let r=await t.files(e.source),n=new y(r),o=await n.loadFaker(t.fakerOpts(e.locale)),s=new d(o),i=n.entities();async function c(p,f){let g=R({each:N(p,s,f)}),h=await(f.count?g.resolve(parseInt(f.count)):m(p,s)),x=JSON.stringify(h,null,2);return {data:h,json:x}}return {entities:i,forge:c}}var B=fileURLToPath(import.meta.url),J=P.dirname(B),F=$.readJSONSync(P.join(J,"../package.json")),v=class{constructor(e,r,n){this.router=e;this.config=r;this.opts=n;let{pathPrefix:o}=this.config.serverOpts(this.opts.pathPrefix);this.prefix=o;}prefix;get querySchema(){return l.object({count:l.string().optional(),uid:l.string().optional(),strategy:l.string().optional()})}async handleQueries(e){let{success:r,data:n,error:o}=await this.querySchema.safeParseAsync(e.query);return r?n:(a.warn(o.message),{})}async register(){let{entities:e,forge:r}=await L(this.config,this.opts);this.router.get("/",(n,o)=>{let s=n.path;o.render("index",{currentPath:s,entities:e,version:F.version});}),this.router.get("/:name",async(n,o)=>{let s=`${n.protocol}://${n.host}/`,i=n.path,c=n.params.name,p=await this.handleQueries(n),f=G.stringify(p,{addQueryPrefix:true}),g=e.get(c.toLowerCase());if(g){let{json:h}=await r(g.type,p),x=g.filepath;o.render("preview",{name:c,filepath:x,currentPath:i,address:s,search:f,json:h,entities:e,version:F.version,prefix:this.prefix});}else o.redirect("/");}),this.router.get(`/${this.prefix}/:name`,async(n,o)=>{try{let s=n.params.name,i=await this.handleQueries(n),c=e.get(s.toLowerCase());if(c){let{data:p}=await r(c.type,i);o.status(200).json(p);}else o.status(400).json({message:"The requested interface is not found"});}catch(s){o.status(500).send(s);}});}};var V=fileURLToPath(import.meta.url),C=P.dirname(V);function X(t,e,r){let{port:n}=e.serverOpts(r.pathPrefix,r.port);t.listen(n,"localhost",async()=>{a.info(`Server: http://localhost:${n}`),console.log(await Q.text("FAKELAB"));});}function H(t,e,r){e.setHeader("x-powered-by","fakelab"),r();}function Z(t){t.disable("x-powered-by"),t.use(k.json()),t.use(M({methods:"GET"})),t.use(k.static(C+"/public")),t.use(H);}function W(t){t.set("views",P.join(C,"views")),t.set("view engine","ejs"),t.use(K),t.set("layout","layouts/main");}async function _(t,e){let r=k(),n=k.Router(),o=q.createServer(r);Z(r),W(r),await new v(n,t,e).register(),r.use(n),X(o,t,e);}async function j(){try{let e=await new ee().resolve({files:["fakelab.config.ts"]});return e||(a.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{a.error("Could not load the config file."),process.exit(1);}}var T=new Command,ne=fileURLToPath(import.meta.url),se=P.dirname(ne),b=$.readJSONSync(P.join(se,"../package.json"));T.name(b.name).description(b.description).version(b.version);var ie=l.object({source:l.string().optional(),pathPrefix:l.string().optional(),port:l.number().int().optional(),locale:l.string().optional()});T.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").action(async t=>{let e=await ie.safeParseAsync(t);e.error&&(a.error(l.treeifyError(e.error).errors.join(`
|
|
2
|
+
`)),process.exit(1));let r=await j();_(r,e.data);});T.parse();
|
package/lib/main.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
type FakerLocale = "af" | "ar" | "az" | "bn" | "cs" | "cy" | "da" | "de" | "dv" | "el" | "en" | "eo" | "es" | "fa" | "fi" | "fr" | "he" | "hr" | "hu" | "hy" | "id" | "it" | "ja" | "ka" | "ko" | "ku" | "lv" | "mk" | "nb" | "ne" | "nl" | "pl" | "pt" | "ro";
|
|
2
|
-
|
|
3
1
|
type ServerOptions = {
|
|
4
2
|
/**
|
|
5
3
|
* @default 5200
|
|
@@ -14,14 +12,24 @@ type FakerEngineOptions = {
|
|
|
14
12
|
locale?: FakerLocale;
|
|
15
13
|
};
|
|
16
14
|
type ConfigOptions = {
|
|
17
|
-
sourcePath: string;
|
|
15
|
+
sourcePath: string | string[];
|
|
18
16
|
server?: ServerOptions;
|
|
19
17
|
faker?: FakerEngineOptions;
|
|
20
18
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
type FakerLocale = "af" | "ar" | "az" | "bn" | "cs" | "cy" | "da" | "de" | "dv" | "el" | "en" | "eo" | "es" | "fa" | "fi" | "fr" | "he" | "hr" | "hu" | "hy" | "id" | "it" | "ja" | "ka" | "ko" | "ku" | "lv" | "mk" | "nb" | "ne" | "nl" | "pl" | "pt" | "ro";
|
|
20
|
+
|
|
21
|
+
declare class Config {
|
|
22
|
+
private readonly opts;
|
|
23
|
+
constructor(opts: ConfigOptions);
|
|
24
|
+
files(_sourcePath?: string): Promise<string[]>;
|
|
25
|
+
serverOpts(prefix?: string, port?: number): Required<ServerOptions>;
|
|
26
|
+
fakerOpts(locale?: FakerLocale): Required<FakerEngineOptions>;
|
|
27
|
+
private tryStat;
|
|
28
|
+
private isReadable;
|
|
29
|
+
private resolveSourcePath;
|
|
30
|
+
private resolveTSFiles;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare function defineConfig(options: ConfigOptions): Config;
|
|
26
34
|
|
|
27
35
|
export { defineConfig };
|
package/lib/main.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import
|
|
1
|
+
import g from'fast-glob';import l from'path';import {stat,access,constants}from'fs/promises';import i from'picocolors';var s=class o{static prefix(t){let r=new Date().toLocaleTimeString();return {info:i.blue,warn:i.yellow,error:i.red,success:i.green,debug:i.magenta}[t](`[${r}] FAKELAB_${t.toUpperCase()}`)}static log(t,r,...e){let n=i.white,c=o.prefix(t);(t==="error"?console.error:t==="warn"?console.warn:console.log)(c,n(r),...e);}static info(t,...r){this.log("info",t,...r);}static warn(t,...r){this.log("warn",t,...r);}static error(t,...r){this.log("error",t,...r);}static success(t,...r){this.log("success",t,...r);}};function p(){let o=Intl.DateTimeFormat().resolvedOptions().locale;if(!o)return "en";let[t]=o.split("-");return t.toLowerCase()}var a=class{constructor(t){this.opts=t;this.files=this.files.bind(this),this.serverOpts=this.serverOpts.bind(this),this.fakerOpts=this.fakerOpts.bind(this);}async files(t){let r=this.resolveSourcePath(t||this.opts.sourcePath),e=Array.from(new Set((await Promise.all(r.map(n=>this.resolveTSFiles(n)))).flat()));return e.length===0&&(s.error(`No Typescript files found in:
|
|
2
|
+
%s`,r.join(`
|
|
3
|
+
`)),process.exit(1)),e}serverOpts(t,r){return {pathPrefix:t||this.opts.server?.pathPrefix||"api",port:r||this.opts.server?.port||5200}}fakerOpts(t){return {locale:t||this.opts.faker?.locale||p()}}async tryStat(t){try{return await stat(t)}catch{return null}}async isReadable(t){try{return await access(t,constants.R_OK),!0}catch{return false}}resolveSourcePath(t){return (Array.isArray(t)?t:[t]).map(e=>l.resolve(e))}async resolveTSFiles(t){let r=l.resolve(t),e=r.endsWith(".ts")?r:r+".ts";if((await this.tryStat(e))?.isFile()){if(!await this.isReadable(e))throw new Error(`Cannot read file: ${e}`);return s.info("Source:",e),[e]}if((await this.tryStat(r))?.isDirectory())return s.info("Source:",r),g("**/*.ts",{cwd:r,absolute:true,ignore:["**/*.d.ts"]});s.error(`Invalid source path: ${t}`),process.exit(1);}};function y(o){return new a(o)}export{y as defineConfig};
|
package/lib/public/css/style.css
CHANGED
|
@@ -41,11 +41,15 @@ pre {
|
|
|
41
41
|
|
|
42
42
|
.sidebar .logo {
|
|
43
43
|
height: 65px;
|
|
44
|
+
text-decoration: none;
|
|
44
45
|
padding-inline: 16px;
|
|
45
46
|
display: flex;
|
|
46
47
|
align-items: center;
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
gap: 6px;
|
|
49
|
+
|
|
50
|
+
> span {
|
|
51
|
+
color: oklch(98.5% 0.002 247.839);
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
.sidebar .sidebar_docs {
|
|
@@ -115,22 +119,46 @@ pre {
|
|
|
115
119
|
padding: 12px 8px;
|
|
116
120
|
border-radius: 4px;
|
|
117
121
|
cursor: pointer;
|
|
122
|
+
display: flex;
|
|
123
|
+
gap: 8px;
|
|
124
|
+
justify-content: space-between;
|
|
125
|
+
align-items: center;
|
|
126
|
+
text-decoration: none;
|
|
127
|
+
&:hover > .item_name {
|
|
128
|
+
color: oklch(98.5% 0.002 247.85);
|
|
129
|
+
}
|
|
130
|
+
&:hover > .item_filepath {
|
|
131
|
+
color: oklch(98.463% 0.00181 249.248 / 0.8);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.sidebar_content_list_item .item_name {
|
|
118
136
|
transition: all 250ms ease-in-out;
|
|
119
137
|
color: oklch(98.463% 0.00181 249.248 / 0.726);
|
|
120
|
-
|
|
138
|
+
user-select: none;
|
|
121
139
|
font-size: 14px;
|
|
122
|
-
|
|
123
|
-
color: oklch(98.5% 0.002 247.839);
|
|
124
|
-
}
|
|
140
|
+
}
|
|
125
141
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
142
|
+
.sidebar_content_list_item .item_filepath {
|
|
143
|
+
transition: all 250ms ease-in-out;
|
|
144
|
+
white-space: nowrap;
|
|
145
|
+
line-clamp: 1;
|
|
146
|
+
text-overflow: ellipsis;
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
color: oklch(98.463% 0.00181 249.248 / 0.6);
|
|
149
|
+
font-style: italic;
|
|
129
150
|
}
|
|
130
151
|
|
|
131
152
|
.sidebar_content_list_item.active {
|
|
132
|
-
color: oklch(98.5% 0.002 247.839);
|
|
133
153
|
background-color: oklch(37.306% 0.03436 260.191 / 0.26);
|
|
154
|
+
|
|
155
|
+
> .item_name {
|
|
156
|
+
color: oklch(98.5% 0.002 247.85);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
> .item_filepath {
|
|
160
|
+
color: oklch(98.463% 0.00181 249.248 / 0.8);
|
|
161
|
+
}
|
|
134
162
|
}
|
|
135
163
|
|
|
136
164
|
.sidebar_content_leading .sidebar_content_title {
|
|
@@ -180,7 +208,13 @@ pre {
|
|
|
180
208
|
|
|
181
209
|
.header .interface_name {
|
|
182
210
|
font-size: 16px;
|
|
183
|
-
color: oklch(98.463% 0.00181 249.248 / 0.
|
|
211
|
+
color: oklch(98.463% 0.00181 249.248 / 0.8);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.interface_name .interface_source_filepath {
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
color: oklch(98.463% 0.00181 249.248 / 0.6);
|
|
217
|
+
font-style: italic;
|
|
184
218
|
}
|
|
185
219
|
|
|
186
220
|
.preview {
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
|
|
3
|
+
<g id="surface1">
|
|
4
|
+
<path style="fill-rule:nonzero;fill:rgb(27.450982%,98.431373%,90.196079%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(27.450982%,98.431373%,90.196079%);stroke-opacity:1;stroke-miterlimit:4;" d="M 460.583333 412.833333 L 326 179.666667 L 326 65.666667 L 334.75 65.666667 C 342 65.666667 347.916667 59.833333 347.916667 52.583333 C 347.916667 45.333333 342 39.416667 334.75 39.416667 L 177.25 39.416667 C 160.083333 39.75 160 65.416667 177.25 65.666667 L 186 65.666667 L 186 179.666667 L 51.416667 412.833333 C 36.083333 439.333333 55.25 472.583333 85.916667 472.583333 L 426.083333 472.583333 C 456.75 472.583333 475.916667 439.333333 460.583333 412.833333 Z M 437.833333 439.5 C 435.5 443.75 431 446.333333 426.083333 446.333333 L 85.916667 446.333333 C 81.083333 446.333333 76.583333 443.75 74.166667 439.5 C 71.666667 435.333333 71.666667 430.166667 74.166667 425.916667 L 151.666667 291.666667 C 162.083333 295.583333 174.666667 298.333333 192 298.333333 C 224 298.333333 240 289.166667 256 280.083333 C 272 270.916667 288 261.833333 320 261.833333 C 328.166667 261.75 336.333333 262.5 344.416667 264 L 437.833333 425.916667 C 440.333333 430.083333 440.333333 435.333333 437.833333 439.5 Z M 437.833333 439.5 " transform="matrix(0.046875,0,0,0.046875,0,0)"/>
|
|
5
|
+
<path style="fill-rule:nonzero;fill:rgb(27.450982%,98.431373%,90.196079%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(27.450982%,98.431373%,90.196079%);stroke-opacity:1;stroke-miterlimit:4;" d="M 218 406.916667 C 218.416667 429.916667 252.666667 429.916667 253 406.916667 C 252.666667 383.916667 218.416667 383.916667 218 406.916667 Z M 218 406.916667 " transform="matrix(0.046875,0,0,0.046875,0,0)"/>
|
|
6
|
+
<path style="fill-rule:nonzero;fill:rgb(27.450982%,98.431373%,90.196079%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(27.450982%,98.431373%,90.196079%);stroke-opacity:1;stroke-miterlimit:4;" d="M 148 354.416667 C 148.416667 377.416667 182.666667 377.416667 183 354.416667 C 182.666667 331.416667 148.416667 331.416667 148 354.416667 Z M 148 354.416667 " transform="matrix(0.046875,0,0,0.046875,0,0)"/>
|
|
7
|
+
<path style="fill-rule:nonzero;fill:rgb(27.450982%,98.431373%,90.196079%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(27.450982%,98.431373%,90.196079%);stroke-opacity:1;stroke-miterlimit:4;" d="M 312.833333 328.166667 C 313.25 351.166667 347.5 351.166667 347.833333 328.166667 C 347.5 305.166667 313.25 305.166667 312.833333 328.166667 Z M 312.833333 328.166667 " transform="matrix(0.046875,0,0,0.046875,0,0)"/>
|
|
8
|
+
</g>
|
|
9
|
+
</svg>
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
function copyAddressUrl() {
|
|
2
|
+
var e = document.getElementById("request_address_url");
|
|
3
3
|
try {
|
|
4
|
-
|
|
4
|
+
navigator.clipboard.writeText(e.textContent);
|
|
5
5
|
} catch (e) {
|
|
6
6
|
console.info(e);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function copyJsonCode() {
|
|
10
|
+
var e = document.getElementById("source_code");
|
|
11
|
+
try {
|
|
12
|
+
navigator.clipboard.writeText(e.textContent);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.info(e);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function downloadJsonCode(e) {
|
|
18
|
+
var t = document.getElementById("source_code").textContent.trim(),
|
|
19
|
+
t = new Blob([t], { type: "application/json" });
|
|
20
|
+
let n = URL.createObjectURL(t);
|
|
21
|
+
var t = e.getAttribute("data-source-name"),
|
|
22
|
+
d = new Date().toISOString().replace(/[:.]/g, "-");
|
|
23
|
+
(e.download = t + `-${d}.json`),
|
|
24
|
+
(e.href = n),
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
URL.revokeObjectURL(n), (e.download = ""), (e.href = "");
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function openSettings() {
|
|
10
30
|
document.getElementById("setting_overlay").setAttribute("data-visible", "true");
|
|
11
31
|
}
|
|
12
32
|
function handleCloseSetting(e) {
|
|
@@ -19,29 +39,29 @@ function submitForm(e) {
|
|
|
19
39
|
e = new FormData(e.target),
|
|
20
40
|
n = e.get("count")?.trim(),
|
|
21
41
|
e = e.get("unique_id_key")?.trim(),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
d = new URLSearchParams(),
|
|
43
|
+
o = document.getElementById("unique_id"),
|
|
44
|
+
i = document.getElementById("unique_id_strategy"),
|
|
25
45
|
n =
|
|
26
|
-
(n && 0 < Number(n) ? (Number(n),
|
|
27
|
-
|
|
28
|
-
e &&
|
|
46
|
+
(n && 0 < Number(n) ? (Number(n), d.set("count", n.toString()), (window.location.search = d.toString())) : (d.delete("count"), t.add("count")),
|
|
47
|
+
o.checked || (d.delete("uid"), t.add("uid")),
|
|
48
|
+
e && o.checked ? (d.set("uid", e), i.checked && d.set("strategy", "uuid"), (window.location.search = d.toString())) : (d.delete("uid"), d.delete("strategy"), t.add("uid")),
|
|
29
49
|
t.has("uid")),
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
o = t.has("count");
|
|
51
|
+
o && n
|
|
32
52
|
? window.location.href.includes("?") && (([href] = window.location.href.split("?")), (window.location.href = href))
|
|
33
|
-
: ((
|
|
53
|
+
: ((o && !n) || (!o && n)) && (window.location.search = d.toString());
|
|
34
54
|
}
|
|
35
55
|
function resetForm(e) {
|
|
36
56
|
e.preventDefault();
|
|
37
57
|
e = new URLSearchParams(window.location.search);
|
|
38
|
-
e.delete("count"), e.delete("uid"), e.delete("
|
|
58
|
+
e.delete("count"), e.delete("uid"), e.delete("strategy"), window.location.href.includes("?") && (window.location.href = window.location.href.split("?")[0]);
|
|
39
59
|
}
|
|
40
60
|
function onChangeGenerateUniqueId(e) {
|
|
41
61
|
var t = document.getElementById("unique_id"),
|
|
42
62
|
n = document.getElementById("unique_id_key_field"),
|
|
43
|
-
|
|
44
|
-
(t.checked = !t.checked), n.setAttribute("data-visible", t.checked.toString()),
|
|
63
|
+
d = document.getElementById("unique_strategy_container");
|
|
64
|
+
(t.checked = !t.checked), n.setAttribute("data-visible", t.checked.toString()), d.setAttribute("data-visible", t.checked.toString());
|
|
45
65
|
}
|
|
46
66
|
function onHandleUniqueIdStrategy(e) {
|
|
47
67
|
var t = document.getElementById("unique_id_strategy");
|
|
@@ -52,11 +72,12 @@ window.addEventListener("click", handleCloseSetting),
|
|
|
52
72
|
var e = document.getElementById("count"),
|
|
53
73
|
t = document.getElementById("unique_id"),
|
|
54
74
|
n = document.getElementById("unique_id_strategy"),
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
d = document.getElementById("unique_id_key"),
|
|
76
|
+
o = document.getElementById("unique_id_key_field"),
|
|
77
|
+
i = document.getElementById("unique_strategy_container"),
|
|
58
78
|
a = new URLSearchParams(window.location.search),
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
c = a.get("count"),
|
|
80
|
+
r = a.get("uid");
|
|
81
|
+
c && (e.value = c),
|
|
82
|
+
r && ((t.checked = !0), (d.value = r), o.setAttribute("data-visible", "true"), i.setAttribute("data-visible", "true"), a.has("strategy")) && (n.checked = !0);
|
|
62
83
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<pre class="code loading"><code class="language-<%= lang %>"><%- code %></code></pre>
|
|
1
|
+
<pre class="code loading"><code id="source_code" class="language-<%= lang %>"><%- code %></code></pre>
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<aside class="sidebar">
|
|
2
|
-
<a class="logo" href="/">Fakelab</a>
|
|
2
|
+
<a class="logo" href="/"><img src="/icons/logo.svg" width="20px" height="20px" /><span>Fakelab</span></a>
|
|
3
3
|
<div class="sidebar_content">
|
|
4
4
|
<div class="sidebar_content_leading">
|
|
5
5
|
<div class="sidebar_content_indicator_icon"></div>
|
|
6
6
|
<span class="sidebar_content_title">Found interfaces</span>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="sidebar_content_list">
|
|
9
|
-
<% entities.forEach((
|
|
9
|
+
<% entities.forEach((entity,name) => { %>
|
|
10
10
|
<a class="sidebar_content_list_item <%= currentPath === `/${name}` ? 'active' : '' %>" href="/<%= name %>">
|
|
11
|
-
<span><%= name %></span>
|
|
11
|
+
<span class="item_name"><%= name %></span>
|
|
12
|
+
<span class="item_filepath"><%= entity.filepath %></span>
|
|
12
13
|
</a>
|
|
13
14
|
<% }) %>
|
|
14
15
|
</div>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<div class="preview_toolbar loading">
|
|
2
|
+
<div class="toolbar_setting">
|
|
3
|
+
<div class="data_fetch_address">
|
|
4
|
+
<div class="address">
|
|
5
|
+
<span class="address_method">GET</span>
|
|
6
|
+
<span id="request_address_url" class="address_url"><%= address %><%= prefix %>/<%= name %><%= search %></span>
|
|
7
|
+
</div>
|
|
8
|
+
<img id="copy_btn" onclick="copyAddressUrl()" class="icon pointer_auto" src="/icons/copy.svg" width="16px" height="16px" />
|
|
9
|
+
</div>
|
|
10
|
+
<div id="overlay_trigger" class="toolbar_setting_option" onclick="openSettings()">
|
|
11
|
+
<img class="icon" src="/icons/settings.svg" width="18px" height="18px" />
|
|
12
|
+
</div>
|
|
13
|
+
<div id="setting_overlay" data-visible="false" class="toolbar_setting_overlay">
|
|
14
|
+
<span class="setting_title">Generator settings</span>
|
|
15
|
+
<form class="setting_form" onsubmit="submitForm(event)">
|
|
16
|
+
<div class="form_input">
|
|
17
|
+
<label for="count">Count</label>
|
|
18
|
+
<input id="count" autocomplete="off" name="count" type="text" />
|
|
19
|
+
<span>Set count field to generate array of data items.</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div style="display: flex; flex-direction: column; gap: 8px">
|
|
22
|
+
<div class="form_input inline">
|
|
23
|
+
<label for="unique_id">Generate unique id</label>
|
|
24
|
+
<div class="switch" onclick="onChangeGenerateUniqueId()">
|
|
25
|
+
<input type="checkbox" id="unique_id" name="unique_id" />
|
|
26
|
+
<span class="slider"></span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div id="unique_id_key_field" class="form_input" data-visible="false">
|
|
30
|
+
<input id="unique_id_key" autocomplete="off" name="unique_id_key" type="text" value="_id" placeholder="unique id key" />
|
|
31
|
+
</div>
|
|
32
|
+
<div id="unique_strategy_container" class="unique_strategy" data-visible="false">
|
|
33
|
+
<span class="unique_id_hint">By default unique id is auto incremented integer.</span>
|
|
34
|
+
<div class="form_input inline">
|
|
35
|
+
<label for="unique_id_strategy">Generate as uuid</label>
|
|
36
|
+
<div class="switch" onclick="onHandleUniqueIdStrategy()">
|
|
37
|
+
<input type="checkbox" id="unique_id_strategy" name="unique_id_strategy" />
|
|
38
|
+
<span class="slider"></span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="form_buttons">
|
|
44
|
+
<button class="form_reset_btn" type="button" onclick="resetForm(event)">Reset</button>
|
|
45
|
+
<button class="form_apply_btn" type="submit">Apply</button>
|
|
46
|
+
</div>
|
|
47
|
+
</form>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="toolbar_actions">
|
|
52
|
+
<div class="toolbar_action_item" onclick="copyJsonCode()">
|
|
53
|
+
<img class="icon" src="/icons/copy.svg" width="18px" height="18px" />
|
|
54
|
+
</div>
|
|
55
|
+
<div class="divider"></div>
|
|
56
|
+
<a class="toolbar_action_item" data-source-name="<%= name %>" onclick="downloadJsonCode(this)">
|
|
57
|
+
<img class="icon" src="/icons/download.svg" width="18px" height="18px" />
|
|
58
|
+
</a>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
package/lib/views/preview.ejs
CHANGED
|
@@ -7,195 +7,7 @@
|
|
|
7
7
|
<div class="preview-loading loading">
|
|
8
8
|
<span class="loader visible"></span><span class="error">Some libraries could not be loaded. check your internet connection and try again.</span>
|
|
9
9
|
</div>
|
|
10
|
-
|
|
11
|
-
<div class="toolbar_setting">
|
|
12
|
-
<div class="data_fetch_address">
|
|
13
|
-
<div class="address">
|
|
14
|
-
<span class="address_method">GET</span>
|
|
15
|
-
<span class="address_url"><%= address %><%= prefix %>/<%= name %><%= search %></span>
|
|
16
|
-
</div>
|
|
17
|
-
<img id="copy_btn" onclick="copy()" class="icon pointer_auto" src="/icons/copy.svg" width="16px" height="16px" />
|
|
18
|
-
</div>
|
|
19
|
-
<div id="overlay_trigger" class="toolbar_setting_option" onclick="open_settings()">
|
|
20
|
-
<img class="icon" src="/icons/settings.svg" width="18px" height="18px" />
|
|
21
|
-
</div>
|
|
22
|
-
<div id="setting_overlay" data-visible="false" class="toolbar_setting_overlay">
|
|
23
|
-
<span class="setting_title">Generator settings</span>
|
|
24
|
-
<form class="setting_form" onsubmit="submitForm(event)">
|
|
25
|
-
<div class="form_input">
|
|
26
|
-
<label for="count">Count</label>
|
|
27
|
-
<input id="count" autocomplete="off" name="count" type="text" />
|
|
28
|
-
<span>Set count field to generate array of data items.</span>
|
|
29
|
-
</div>
|
|
30
|
-
<div style="display: flex; flex-direction: column; gap: 8px">
|
|
31
|
-
<div class="form_input inline">
|
|
32
|
-
<label for="unique_id">Generate unique id</label>
|
|
33
|
-
<div class="switch" onclick="onChangeGenerateUniqueId()">
|
|
34
|
-
<input type="checkbox" id="unique_id" name="unique_id" />
|
|
35
|
-
<span class="slider"></span>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
<div id="unique_id_key_field" class="form_input" data-visible="false">
|
|
39
|
-
<input id="unique_id_key" autocomplete="off" name="unique_id_key" type="text" value="_id" placeholder="unique id key" />
|
|
40
|
-
</div>
|
|
41
|
-
<div id="unique_strategy_container" class="unique_strategy" data-visible="false">
|
|
42
|
-
<span class="unique_id_hint">By default unique id is auto incremented integer.</span>
|
|
43
|
-
<div class="form_input inline">
|
|
44
|
-
<label for="unique_id_strategy">Generate as uuid</label>
|
|
45
|
-
<div class="switch" onclick="onHandleUniqueIdStrategy()">
|
|
46
|
-
<input type="checkbox" id="unique_id_strategy" name="unique_id_strategy" />
|
|
47
|
-
<span class="slider"></span>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="form_buttons">
|
|
53
|
-
<button class="form_reset_btn" type="button" onclick="resetForm(event)">Reset</button>
|
|
54
|
-
<button class="form_apply_btn" type="submit">Apply</button>
|
|
55
|
-
</div>
|
|
56
|
-
</form>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
<div class="toolbar_actions">
|
|
61
|
-
<div class="toolbar_action_item">
|
|
62
|
-
<img class="icon" src="/icons/copy.svg" width="18px" height="18px" />
|
|
63
|
-
</div>
|
|
64
|
-
<div class="divider"></div>
|
|
65
|
-
<div class="toolbar_action_item">
|
|
66
|
-
<img class="icon" src="/icons/download.svg" width="18px" height="18px" />
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
<%- include("components/code", { lang: "json", code: json }) %>
|
|
10
|
+
<%- include("components/toolbar", { address, prefix, name, search }) %> <%- include("components/code", { lang: "json", code: json }) %>
|
|
71
11
|
</div>
|
|
72
12
|
</main>
|
|
73
13
|
<script src="/js/fakelab.min.js"></script>
|
|
74
|
-
<!-- <script>
|
|
75
|
-
const address_url = document.querySelector(".address_url");
|
|
76
|
-
|
|
77
|
-
async function copy() {
|
|
78
|
-
try {
|
|
79
|
-
await navigator.clipboard.writeText(address_url.textContent);
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.info(err);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function open_settings() {
|
|
86
|
-
const element = document.getElementById("setting_overlay");
|
|
87
|
-
element.setAttribute("data-visible", "true");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function handleCloseSetting(event) {
|
|
91
|
-
const element = document.getElementById("setting_overlay");
|
|
92
|
-
|
|
93
|
-
if (!event.target) return;
|
|
94
|
-
if (!element.contains(event.target) && event.target.id !== "overlay_trigger") {
|
|
95
|
-
element.setAttribute("data-visible", "false");
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
window.addEventListener("click", handleCloseSetting);
|
|
100
|
-
|
|
101
|
-
window.addEventListener("DOMContentLoaded", () => {
|
|
102
|
-
const countEl = document.getElementById("count");
|
|
103
|
-
const uniqueIdEl = document.getElementById("unique_id");
|
|
104
|
-
const strategyEl = document.getElementById("unique_id_strategy");
|
|
105
|
-
const uniqueIdKeyEl = document.getElementById("unique_id_key");
|
|
106
|
-
const keyEl = document.getElementById("unique_id_key_field");
|
|
107
|
-
const strategyContainerEl = document.getElementById("unique_strategy_container");
|
|
108
|
-
const params = new URLSearchParams(window.location.search);
|
|
109
|
-
const countValue = params.get("count");
|
|
110
|
-
const uniqueIdValue = params.get("uid");
|
|
111
|
-
if (countValue) countEl.value = countValue;
|
|
112
|
-
if (uniqueIdValue) {
|
|
113
|
-
uniqueIdEl.checked = true;
|
|
114
|
-
uniqueIdKeyEl.value = uniqueIdValue;
|
|
115
|
-
keyEl.setAttribute("data-visible", "true");
|
|
116
|
-
strategyContainerEl.setAttribute("data-visible", "true");
|
|
117
|
-
if (params.has("s")) {
|
|
118
|
-
strategyEl.checked = true;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
function submitForm(event) {
|
|
124
|
-
event.preventDefault();
|
|
125
|
-
const resetReasons = new Set();
|
|
126
|
-
const formData = new FormData(event.target);
|
|
127
|
-
const countValue = formData.get("count")?.trim();
|
|
128
|
-
const uniqueIdValue = formData.get("unique_id_key")?.trim();
|
|
129
|
-
const params = new URLSearchParams();
|
|
130
|
-
|
|
131
|
-
const switcher = document.getElementById("unique_id");
|
|
132
|
-
const strategySwitcher = document.getElementById("unique_id_strategy");
|
|
133
|
-
|
|
134
|
-
if (countValue && Number(countValue) > 0) {
|
|
135
|
-
const valueAsNumber = Number(countValue);
|
|
136
|
-
|
|
137
|
-
params.set("count", countValue.toString());
|
|
138
|
-
window.location.search = params.toString();
|
|
139
|
-
} else {
|
|
140
|
-
params.delete("count");
|
|
141
|
-
resetReasons.add("count");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!switcher.checked) {
|
|
145
|
-
params.delete("uid");
|
|
146
|
-
resetReasons.add("uid");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (uniqueIdValue && switcher.checked) {
|
|
150
|
-
params.set("uid", uniqueIdValue);
|
|
151
|
-
|
|
152
|
-
if (strategySwitcher.checked) {
|
|
153
|
-
params.set("s", "uuid");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
window.location.search = params.toString();
|
|
157
|
-
} else {
|
|
158
|
-
params.delete("uid");
|
|
159
|
-
params.delete("s");
|
|
160
|
-
resetReasons.add("uid");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const hasUidReason = resetReasons.has("uid");
|
|
164
|
-
const hasCountReason = resetReasons.has("count");
|
|
165
|
-
|
|
166
|
-
if (hasCountReason && hasUidReason) {
|
|
167
|
-
if (window.location.href.includes("?")) {
|
|
168
|
-
[href] = window.location.href.split("?");
|
|
169
|
-
|
|
170
|
-
window.location.href = href;
|
|
171
|
-
}
|
|
172
|
-
} else if ((hasCountReason && !hasUidReason) || (!hasCountReason && hasUidReason)) {
|
|
173
|
-
window.location.search = params.toString();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function resetForm(event) {
|
|
178
|
-
event.preventDefault();
|
|
179
|
-
const params = new URLSearchParams(window.location.search);
|
|
180
|
-
params.delete("count");
|
|
181
|
-
params.delete("uid");
|
|
182
|
-
params.delete("s");
|
|
183
|
-
if (window.location.href.includes("?")) {
|
|
184
|
-
window.location.href = window.location.href.split("?")[0];
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function onChangeGenerateUniqueId(element) {
|
|
189
|
-
const switcher = document.getElementById("unique_id");
|
|
190
|
-
const key = document.getElementById("unique_id_key_field");
|
|
191
|
-
const strategy = document.getElementById("unique_strategy_container");
|
|
192
|
-
switcher.checked = !switcher.checked;
|
|
193
|
-
key.setAttribute("data-visible", switcher.checked.toString());
|
|
194
|
-
strategy.setAttribute("data-visible", switcher.checked.toString());
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function onHandleUniqueIdStrategy(element) {
|
|
198
|
-
const strategy = document.getElementById("unique_id_strategy");
|
|
199
|
-
strategy.checked = !strategy.checked;
|
|
200
|
-
}
|
|
201
|
-
</script> -->
|
package/package.json
CHANGED
package/lib/views/error.ejs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<h1>ERROR <%= name %> data not found!</h1>
|