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.
- package/README.md +29 -7
- package/lib/cli.js +2 -2
- 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
|
|
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: "./
|
|
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
|
-
`/
|
|
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
|
-
|
|
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
|
-
|
|
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 ./
|
|
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
|
|
2
|
-
`)),process.exit(1));let r=await
|
|
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();
|