fakelab 0.0.9 → 0.0.10

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.
Files changed (3) hide show
  1. package/README.md +29 -7
  2. package/lib/cli.js +2 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,13 +21,13 @@ yarn add -D fakelab
21
21
 
22
22
  ## Usage/Examples
23
23
 
24
- create `fakelab.config.ts` file in the project root. and reference your app interfaces files.
24
+ create `fakelab.config.ts` file in the project root. and reference your typescript files.
25
25
 
26
26
  ```typescript
27
27
  import { defineConfig } from "fakelab";
28
28
 
29
29
  export default defineConfig({
30
- sourcePath: "./interfaces", // can set one/multiple directory(s) or typescript file(s).
30
+ sourcePath: "./types", // 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
  });
@@ -37,9 +37,29 @@ Fakelab allows you to control generated mock data using JSDoc tags.
37
37
  You simply annotate your TypeScript interfaces with the @faker tag, and Fakelab uses the corresponding [faker](https://fakerjs.dev/)
38
38
  method when generating mock values.
39
39
 
40
- `/interfaces/user.ts`:
40
+ `/other/post.ts`:
41
41
 
42
42
  ```typescript
43
+ export type Post = {
44
+ id: string;
45
+ title: string;
46
+ };
47
+ ```
48
+
49
+ `/other/profile.ts`:
50
+
51
+ ```typescript
52
+ export type Profile = {
53
+ id: string;
54
+ };
55
+ ```
56
+
57
+ `/types/user.ts`:
58
+
59
+ ```typescript
60
+ export { type Profile } from "../other/profile";
61
+ import { type Post } from "../other/post";
62
+
43
63
  export interface User {
44
64
  /** @faker string.uuid */
45
65
  id: string;
@@ -47,7 +67,8 @@ export interface User {
47
67
  /** @faker person.fullName */
48
68
  name: string;
49
69
 
50
- /** @faker number.int */
70
+ // Use it as a function to pass the arguments.
71
+ /** @faker number.int({ max: 10 }) */
51
72
  age: number;
52
73
 
53
74
  /** @faker datatype.boolean */
@@ -56,11 +77,12 @@ export interface User {
56
77
  /** @faker location.streetAddress */
57
78
  address: string;
58
79
 
59
- /** @faker word.words */
60
- tags: string[];
80
+ posts: Post[];
61
81
  }
62
82
  ```
63
83
 
84
+ **NOTE:** Fakelab only supports `interfaces`, `types`, `named export declarations`.
85
+
64
86
  ## Server Command
65
87
 
66
88
  Run:
@@ -85,7 +107,7 @@ npx fakelab serve [options]
85
107
  npx fakelab serve
86
108
 
87
109
  # Custom source and port
88
- npx fakelab serve -s ./interfaces -p 4000
110
+ npx fakelab serve -s ./types -p 4000
89
111
 
90
112
  # Custom API prefix and locale
91
113
  npx fakelab serve --pathPrefix /v1 --locale fr
package/lib/cli.js CHANGED
@@ -1,2 +1,2 @@
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();
1
+ import P from'path';import {fileURLToPath}from'url';import M from'fs-extra';import m from'zod';import {Command}from'commander';import k from'express';import J from'cors';import K from'express-ejs-layouts';import q from'http';import'ejs';import $ 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 c=class o{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 t=u.white,s=o.prefix(e);(e==="error"?console.error:e==="warn"?console.warn:console.log)(s,t(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 y=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 t=>{let s=t.getTypeAtLocation(t.getValueDeclarationOrThrow());n[t.getName()]=await r(s,this,this.readJSDocTags(t));})),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[t]=n.getText();if(!t)return;let s=t.text.trim().match(this.FAKER_TAG_REGEX);if(!s)return;let[,i,a]=s,p=this.evalArgs(a);return {path:i,args:p}})}execute(e,r){if(!e)return r();let n=e.path.split("."),t=this.faker;for(let s of n)t=t[s],t||(c.error("Invalid faker module path:",e.path),process.exit(1));typeof t!="function"&&(c.error("Unresolvable faker function.",e.path),process.exit(1));try{return e.args?t(e.args):t()}catch{return c.error("Passed invalid arguments to faker function."),t()}}};var d=class{constructor(e){this.files=e;let n=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=n.flatMap(t=>{let s=t.getInterfaces(),i=t.getTypeAliases(),a=t.getExportDeclarations().flatMap(p=>p.getNamedExports().flatMap(f=>f.getLocalTargetDeclarations()));return [...s,...i,...a]});}__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(),t=r.getType(),s=this.normalizePath(process.cwd()),a=this.normalizePath(r.getSourceFile().getDirectoryPath()).replace(s,""),p=r.getSourceFile().getBaseName(),f=`${a}/${p}`;return [n,{type:t,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 l(o,e,r=[],n=0){if(o.isString())return e.string(r[n]);if(o.isNumber())return e.int(r[n]);if(o.isBoolean())return e.bool(r[n]);if(o.isBigInt())return e.bigInt(r[n]);if(o.isBooleanLiteral())return e.litbool(o.getText());if(o.isLiteral())return o.getLiteralValue();if(!o.isUndefined()){if(o.isUnion()){let t=o.getUnionTypes();return await e.union(t.map((s,i)=>l(s,e,r,i)))}if(o.isIntersection()){let t=o.getIntersectionTypes();return await e.intersection(t.map((s,i)=>l(s,e,r,i)))}if(o.isArray()){let t=o.getArrayElementTypeOrThrow();return [await l(t,e,r,n)]}if(o.isObject()){let t=o.getProperties();return await e.object(t,(s,i,a)=>l(s,i,a,n))}return null}}function D({each:o}){return {resolve:async r=>await Promise.all(Array.from({length:r},(n,t)=>o(t)))}}function R(o,e){return o==="uuid"?v4():e+1}function N(o,e,r){return async n=>{let t=await l(o,e);return r.uid&&typeof t=="object"?{[r.uid]:R(r.strategy,n),...t}:t}}async function L(o,e){let r=await o.files(e.source),n=new d(r),t=await n.loadFaker(o.fakerOpts(e.locale)),s=new y(t),i=n.entities();async function a(p,f){let g=D({each:N(p,s,f)}),h=await(f.count?g.resolve(parseInt(f.count)):l(p,s)),x=JSON.stringify(h,null,2);return {data:h,json:x}}return {entities:i,forge:a}}var U=fileURLToPath(import.meta.url),B=P.dirname(U),A=M.readJSONSync(P.join(B,"../package.json")),v=class{constructor(e,r,n){this.router=e;this.config=r;this.opts=n;let{pathPrefix:t}=this.config.serverOpts(this.opts.pathPrefix);this.prefix=t;}prefix;get querySchema(){return m.object({count:m.string().optional(),uid:m.string().optional(),strategy:m.string().optional()})}async handleQueries(e){let{success:r,data:n,error:t}=await this.querySchema.safeParseAsync(e.query);return r?n:(c.warn(t.message),{})}async register(){let{entities:e,forge:r}=await L(this.config,this.opts);this.router.get("/",(n,t)=>{let s=n.path;t.render("index",{currentPath:s,entities:e,version:A.version});}),this.router.get("/:name",async(n,t)=>{let s=`${n.protocol}://${n.host}/`,i=n.path,a=n.params.name,p=await this.handleQueries(n),f=$.stringify(p,{addQueryPrefix:true}),g=e.get(a.toLowerCase());if(g){let{json:h}=await r(g.type,p),x=g.filepath;t.render("preview",{name:a,filepath:x,currentPath:i,address:s,search:f,json:h,entities:e,version:A.version,prefix:this.prefix});}else t.redirect("/");}),this.router.get(`/${this.prefix}/:name`,async(n,t)=>{try{let s=n.params.name,i=await this.handleQueries(n),a=e.get(s.toLowerCase());if(a){let{data:p}=await r(a.type,i);t.status(200).json(p);}else t.status(400).json({message:"The requested interface is not found"});}catch(s){t.status(500).send(s);}});}};var V=fileURLToPath(import.meta.url),C=P.dirname(V);function X(o,e,r){let{port:n}=e.serverOpts(r.pathPrefix,r.port);o.listen(n,"localhost",async()=>{c.info(`Server: http://localhost:${n}`),console.log(await Q.text("FAKELAB"));});}function H(o,e,r){e.setHeader("x-powered-by","fakelab"),r();}function Z(o){o.disable("x-powered-by"),o.use(k.json()),o.use(J({methods:"GET"})),o.use(k.static(C+"/public")),o.use(H);}function W(o){o.set("views",P.join(C,"views")),o.set("view engine","ejs"),o.use(K),o.set("layout","layouts/main");}async function E(o,e){let r=k(),n=k.Router(),t=q.createServer(r);Z(r),W(r),await new v(n,o,e).register(),r.use(n),X(t,o,e);}async function _(){try{let e=await new ee().resolve({files:["fakelab.config.ts"]});return e||(c.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{c.error("Could not load the config file."),process.exit(1);}}var b=new Command,ne=fileURLToPath(import.meta.url),se=P.dirname(ne),T=M.readJSONSync(P.join(se,"../package.json"));b.name(T.name).description(T.description).version(T.version);var ie=m.object({source:m.string().optional(),pathPrefix:m.string().optional(),port:m.number().int().optional(),locale:m.string().optional()});b.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 o=>{let e=await ie.safeParseAsync(o);e.error&&(c.error(m.treeifyError(e.error).errors.join(`
2
+ `)),process.exit(1));let r=await _();E(r,e.data);});b.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fakelab",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A easy-config mock server for frontend developers.",