fakelab 0.0.23 → 0.0.25
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 +23 -19
- package/lib/cli.js +8 -10
- package/lib/main.d.ts +14 -30
- package/lib/main.js +12 -141
- package/lib/public/css/style.css +1 -1
- package/lib/runtime.d.ts +48 -0
- package/lib/views/components/sidebar.ejs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -92,7 +92,6 @@ export default defineConfig({
|
|
|
92
92
|
sourcePath: ["./types", "./fixtures/**/*.ts"], // supports glob pattern
|
|
93
93
|
faker: { locale: "en" }, // optional
|
|
94
94
|
server: { pathPrefix: "api/v1", port: 8080 }, // optional
|
|
95
|
-
browser: { expose: { mode: "module" } }, // optional
|
|
96
95
|
});
|
|
97
96
|
```
|
|
98
97
|
|
|
@@ -185,15 +184,6 @@ import { fakelab } from "fakelab/browser";
|
|
|
185
184
|
|
|
186
185
|
const users = await fakelab.fetch("User", 10);
|
|
187
186
|
|
|
188
|
-
console.log(users);
|
|
189
|
-
|
|
190
|
-
// or
|
|
191
|
-
|
|
192
|
-
// can be enabled as a global object
|
|
193
|
-
import "fakelab/browser";
|
|
194
|
-
|
|
195
|
-
const users = await fakelab.fetch("User", 10);
|
|
196
|
-
|
|
197
187
|
console.log(users);
|
|
198
188
|
```
|
|
199
189
|
|
|
@@ -283,8 +273,15 @@ This is useful when you want to:
|
|
|
283
273
|
### Snapshot Options
|
|
284
274
|
|
|
285
275
|
```ts
|
|
276
|
+
export type SnapshotDataSource = {
|
|
277
|
+
url: string;
|
|
278
|
+
name: string;
|
|
279
|
+
headers?: SourceHeaders;
|
|
280
|
+
};
|
|
281
|
+
|
|
286
282
|
export type SnapshotOptions = {
|
|
287
283
|
enabled: boolean;
|
|
284
|
+
sources?: SnapshotDataSource[];
|
|
288
285
|
};
|
|
289
286
|
```
|
|
290
287
|
|
|
@@ -297,10 +294,11 @@ npx fakelab snapshot [url] [options]
|
|
|
297
294
|
|
|
298
295
|
### Options
|
|
299
296
|
|
|
300
|
-
| Option
|
|
301
|
-
|
|
|
302
|
-
| `--
|
|
303
|
-
| `--
|
|
297
|
+
| Option | Alias | Description |
|
|
298
|
+
| -------------------- | ----- | ------------------------------ |
|
|
299
|
+
| `--source <string>` | `-s` | specify snapshot source name |
|
|
300
|
+
| `--refresh <string>` | `-r` | refresh the specified snapshot |
|
|
301
|
+
| `--delete <string>` | `-d` | delete the specified snapshot |
|
|
304
302
|
|
|
305
303
|
### Examples
|
|
306
304
|
|
|
@@ -308,14 +306,16 @@ npx fakelab snapshot [url] [options]
|
|
|
308
306
|
# Basic usage
|
|
309
307
|
npx fakelab snapshot https://jsonplaceholder.typicode.com/todos
|
|
310
308
|
|
|
311
|
-
#
|
|
312
|
-
npx fakelab snapshot https://jsonplaceholder.typicode.com/todos --
|
|
309
|
+
# specify a name for captured source
|
|
310
|
+
npx fakelab snapshot https://jsonplaceholder.typicode.com/todos --source Todo
|
|
313
311
|
|
|
314
|
-
#
|
|
315
|
-
npx fakelab snapshot
|
|
312
|
+
# refresh the existing snapshot
|
|
313
|
+
npx fakelab snapshot --refresh Todo
|
|
316
314
|
|
|
315
|
+
# delete the existing snapshot
|
|
316
|
+
npx fakelab snapshot --delete Todo
|
|
317
317
|
|
|
318
|
-
# update all
|
|
318
|
+
# update all snapshots
|
|
319
319
|
npx fakelab snapshot
|
|
320
320
|
```
|
|
321
321
|
|
|
@@ -372,6 +372,7 @@ npx fakelab serve [options]
|
|
|
372
372
|
| `--pathPrefix <prefix>` | `-x` | Prefix for all generated API routes |
|
|
373
373
|
| `--locale <locale>` | `-l` | Locale used for fake data generation |
|
|
374
374
|
| `--port <number>` | `-p` | Port to run the server on |
|
|
375
|
+
| `--fresh-snapshots` | `-f` | capture or refresh all snapshots |
|
|
375
376
|
|
|
376
377
|
### Examples
|
|
377
378
|
|
|
@@ -384,6 +385,9 @@ npx fakelab serve -s ./types -p 4000
|
|
|
384
385
|
|
|
385
386
|
# Custom API prefix and locale
|
|
386
387
|
npx fakelab serve --pathPrefix /v1 --locale fr
|
|
388
|
+
|
|
389
|
+
# refresh existing snapshots
|
|
390
|
+
npx fakelab serve --fresh-snapshots
|
|
387
391
|
```
|
|
388
392
|
|
|
389
393
|
## Related
|
package/lib/cli.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import d from'path';import u from'fs-extra';import {Command}from'commander';import T from'express';import K from'cors';import X from'http';import Y from'express-ejs-layouts';import {Project}from'ts-morph';import b from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import q from'qs';import Z from'figlet';import {bundleRequire}from'bundle-require';import te from'joycon';var $=new Intl.ListFormat("en",{style:"long",type:"unit"}),p=class{static label(e){switch(e){case "info":return b.blueBright(e.toUpperCase());case "error":return b.redBright(e.toUpperCase());case "warn":return b.yellowBright(e.toUpperCase());case "success":return b.greenBright(e.toUpperCase())}}static log(e,t){return [b.dim(`[${new Date().toISOString()}]`),this.label(e),t].join(" ")}static info(e,...t){console.log(this.log("info",e),...t);}static warn(e,...t){console.log(this.log("warn",e),...t);}static error(e,...t){console.error(this.log("error",e),...t);}static success(e,...t){console.log(this.log("success",e),...t);}static debug(e,...t){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",e),...t);}static list(e){return $.format(e)}};var v=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,t){let s={};return await Promise.all(e.map(async r=>{let a=r.getTypeAtLocation(r.getValueDeclarationOrThrow());s[r.getName()]=await t(a,this,this.readJSDocTags(r));})),s}async union(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}async intersection(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let t=e.getJsDocTags().filter(s=>s.getName()===this.JSDOC_FAKER_FIELD);return t.length===0?[]:t.map(s=>{let[r]=s.getText();if(!r)return;let a=r.text.trim().match(this.FAKER_TAG_REGEX);if(!a)return;let[,i,o]=a,c=this.evalArgs(o);return {path:i,args:c}})}execute(e,t){if(!e)return t();let s=e.path.split("."),r=this.faker;for(let a of s)r=r[a],r||(p.error("Invalid faker module path: (%s)",e.path),process.exit(1));typeof r!="function"&&(p.error("Unresolvable faker function. (%s)",e.path),process.exit(1));try{return e.args?r(e.args):r()}catch{return p.error("Passed invalid arguments to faker function."),r()}}};var f=d.dirname(fileURLToPath(import.meta.url)),m=process.cwd();var x=class{constructor(e,t){this.files=e;this.database=t;let r=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=r.flatMap(a=>{let i=a.getInterfaces(),o=a.getTypeAliases(),c=a.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(h=>h.getLocalTargetDeclarations()));return [...i,...o,...c]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(e){return await e()}normalizePath(e){return e.split(d.sep).join(d.posix.sep)}generateInFileEntitiyMap(e){let s=`
|
|
2
2
|
interface Runtime$ {
|
|
3
|
-
${
|
|
3
|
+
${[...new Set(e.map(r=>{let a=r.getName(),i=r.getSourceFile().getFilePath();return `${a}: import("${i}").${a}`}))].join(`
|
|
4
4
|
`)}
|
|
5
|
-
}`;s.mode==="global"&&(r=`
|
|
6
|
-
|
|
7
|
-
}`),f.appendFile(m.resolve(h,this.config.RUNTIME_DECL_FILENAME),r);}address(e,t){let s=this.normalizePath(d),a=`${e.replace(s,"")}/${t}`;return a.startsWith("/.fakelab")&&(a=a.replace("/.fakelab","")),a}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let s=t.getName().toLowerCase(),r=t.getType(),a=this.normalizePath(t.getSourceFile().getDirectoryPath()),i=t.getSourceFile().getBaseName(),o=this.address(a,i),c=m.resolve(this.database.DATABASE_DIR,`${s}.json`),l=this.address(this.normalizePath(this.database.DATABASE_DIR),m.basename(c)),u=a.includes("/.fakelab/snapshots"),b=await JSONFilePreset(c,[]);return [s,{type:r,filepath:o,snapshot:u,table:b,tablepath:l}]}));return new Map(e.sort((t,s)=>Number(t[1].snapshot)-Number(s[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function g(n,e,t=[],s=0){if(n.isString())return e.string(t[s]);if(n.isNumber())return e.int(t[s]);if(n.isBoolean())return e.bool(t[s]);if(n.isBigInt())return e.bigInt(t[s]);if(n.isBooleanLiteral())return e.litbool(n.getText());if(n.isLiteral())return n.getLiteralValue();if(!n.isUndefined()){if(n.isUnion()){let r=n.getUnionTypes();return await e.union(r.map((a,i)=>g(a,e,t,i)))}if(n.isIntersection()){let r=n.getIntersectionTypes();return await e.intersection(r.map((a,i)=>g(a,e,t,i)))}if(n.isArray()){let r=n.getArrayElementTypeOrThrow();return [await g(r,e,t,s)]}if(n.isObject()){let r=n.getProperties();return await e.object(r,(a,i,o)=>g(a,i,o,s))}return null}}function J({each:n}){return {resolve:async t=>await Promise.all(Array.from({length:t},n))}}async function P(n,e,t){let s=await n.files(e.source),r=new x(s,n,t),a=await r.entities(),i=await r.initFakerLibrary(n.options.faker(e.locale)),o=new v(i);async function c(l,u){let b=J({each:()=>g(l,o)}),F=await(u.count?b.resolve(parseInt(u.count)):g(l,o)),O=JSON.stringify(F,null,2);return {data:F,json:O}}return {entities:a,build:c}}var k=class{constructor(e,t,s){this.builder=e;this.database=t;this.pkg=s;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,a=t.params.name,i=await this.handleQueries(t),o=q.stringify(i,{addQueryPrefix:true}),c=this.builder.entities.get(a.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,i),u=c.filepath;s.render("preview",{name:a,suffix:c.snapshot?"(snapshot)":"",filepath:u,address:r,search:o,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else s.redirect("/");}}db(){return (e,t)=>{this.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,a=t.params.name,i=this.builder.entities.get(a.toLowerCase());if(!this.database.enabled())s.redirect("/");else if(i){await i.table.read();let c=i.table.data.length>0,l=JSON.stringify(i.table.data,null,2),u=i.filepath;s.render("table",{name:a,filepath:u,address:r,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else s.redirect("/database");}}};var A=class{constructor(e,t,s){this.builder=e;this.network=t;this.database=s;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:s}=this.network.state("offline");return e.status(t).json({message:s}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:s}=this.network.state("error");return e.status(t).json({message:s}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);t.status(200).json(i);}else t.status(400).json({message:"The entity is not exists"});}catch(s){t.status(500).send(s);}}}getTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),t.status(200).json(r.table.data)):t.status(400).json({message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}updateTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(...Array.isArray(i)?i:[i])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.body?.count||1,r=e.body?.strategy||"reset",a=e.params.name,i=this.builder.entities.get(a.toLowerCase());if(i){let{data:o}=await this.builder.build(i.type,{count:s});if(await i.table.read(),r==="once"&&i.table.data.length>0)return t.status(200).json({message:`${a} entity was seeded once before.`});await i.table.update(c=>{r!=="merge"&&(c.length=0);let l=Array.isArray(o)?o:[o];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){t.status(500).send(s);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.update(a=>a.length=0),t.status(200).json({success:!0})):t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){console.log({error:s}),t.status(500).send(s);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(i)),t.status(301).redirect(`/database/${s.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),r.table.data.length>0&&await r.table.update(a=>a.length=0),t.status(301).redirect(`/database/${s.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var K=f.readJSONSync(m.join(h,"../package.json")),S=class{constructor(e,t,s,r,a){this.router=e;this.config=t;this.network=s;this.database=r;this.serverCLIOptions=a;let{pathPrefix:i}=this.config.options.server(this.serverCLIOptions.pathPrefix,this.serverCLIOptions.port);this.prefix=i;}prefix;async register(){let e=await P(this.config,this.serverCLIOptions,this.database),t=new k(e,this.database,K),s=new A(e,this.network,this.database);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.db()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,s.entity()),this.router.get(`/${this.prefix}/database/:name`,s.getTable()),this.router.post(`/${this.prefix}/database/:name`,s.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,s.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var D=class n{constructor(e){this.config=e;this.options=this.config.options.network(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new n(e)}timeout(){let e=this.chance(this.options?.timeoutRate);return e&&p.debug("Network timeout..."),e}error(){let e=this.chance(this.options?.errorRate);return e&&p.debug("Network error..."),e}state(e){switch(e){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(p.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options?.offline??false}middleware(e,t,s){let r=this.options?.errorRate||0,a=this.options?.timeoutRate||0,i=this.options?.offline??false,o=`delay=${this.resolveDelay()},error=${r},timeout=${a},offline=${i}`;t.setHeader("X-Fakelab-Network",o),s();}resolveDelay(){let e=this.options?.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};async function T(){try{let e=await new V().resolve({files:["fakelab.config.ts"]});return e||(p.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{p.error("Could not load the config file."),process.exit(1);}}var R=class n{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||f.rmSync(this.DATABASE_DIR,{force:true,recursive:true});}options;DATABASE_DIR=m.resolve(d,".fakelab/db");static register(e){return new n(e)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled())try{await f.ensureDir(this.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch{p.error("Could not create database.");}}async modifyGitignoreFile(e){try{let t=m.resolve(d,".gitignore");if((await f.readFile(t,{encoding:"utf8"})).split(`
|
|
8
|
-
`).
|
|
9
|
-
|
|
10
|
-
`)}
|
|
11
|
-
`).some(r=>r.trim()===e.trim()))return;await f.appendFile(t,`
|
|
12
|
-
${e}`);}catch{}}};var N=new Command,_=f.readJSONSync(m.join(h,"../package.json"));N.name(_.name).description(_.description).version(_.version);N.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 n=>{C.init(n).start();});N.command("snapshot").description("snapshot a endpoint json response to a typescript type").argument("[string]","url to snapshot").option("-n, --name <char>","snapshot type name").option("-u, --update","force update the snapshot").action(async(n,e)=>{I.init(n,e).capture();});N.parse();
|
|
5
|
+
}`;u.appendFile(d.resolve(f,"runtime.d.ts"),s);}address(e,t){let s=this.normalizePath(m);return `${e.replace(s,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let s=t.getName().toLowerCase(),r=t.getType(),a=this.normalizePath(t.getSourceFile().getDirectoryPath()),i=t.getSourceFile().getBaseName(),o=this.address(a,i),c=d.resolve(this.database.DATABASE_DIR,`${s}.json`),l=this.address(this.normalizePath(this.database.DATABASE_DIR),d.basename(c)),h=a.includes("/.fakelab/snapshots"),I=await JSONFilePreset(c,[]);return [s,{type:r,filepath:o,snapshot:h,table:I,tablepath:l}]}));return new Map(e.sort((t,s)=>Number(t[1].snapshot)-Number(s[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function g(n,e,t=[],s=0){if(n.isString())return e.string(t[s]);if(n.isNumber())return e.int(t[s]);if(n.isBoolean())return e.bool(t[s]);if(n.isBigInt())return e.bigInt(t[s]);if(n.isBooleanLiteral())return e.litbool(n.getText());if(n.isLiteral())return n.getLiteralValue();if(!n.isUndefined()){if(n.isUnion()){let r=n.getUnionTypes();return await e.union(r.map((a,i)=>g(a,e,t,i)))}if(n.isIntersection()){let r=n.getIntersectionTypes();return await e.intersection(r.map((a,i)=>g(a,e,t,i)))}if(n.isArray()){let r=n.getArrayElementTypeOrThrow();return [await g(r,e,t,s)]}if(n.isObject()){let r=n.getProperties();return await e.object(r,(a,i,o)=>g(a,i,o,s))}return null}}function J({each:n}){return {resolve:async t=>await Promise.all(Array.from({length:t},n))}}async function P(n,e,t){let s=await n.files(e.source),r=new x(s,t),a=await r.entities(),i=await r.initFakerLibrary(n.options.faker(e.locale)),o=new v(i);async function c(l,h){let I=J({each:()=>g(l,o)}),_=await(h.count?I.resolve(parseInt(h.count)):g(l,o)),j=JSON.stringify(_,null,2);return {data:_,json:j}}return {entities:a,build:c}}var S=class{constructor(e,t,s){this.builder=e;this.database=t;this.pkg=s;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,a=t.params.name,i=await this.handleQueries(t),o=q.stringify(i,{addQueryPrefix:true}),c=this.builder.entities.get(a.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,i),h=c.filepath;s.render("preview",{name:a,suffix:c.snapshot?"(snapshot)":"",filepath:h,address:r,search:o,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else s.redirect("/");}}db(){return (e,t)=>{this.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,a=t.params.name,i=this.builder.entities.get(a.toLowerCase());if(!this.database.enabled())s.redirect("/");else if(i){await i.table.read();let c=i.table.data.length>0,l=JSON.stringify(i.table.data,null,2),h=i.filepath;s.render("table",{name:a,filepath:h,address:r,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else s.redirect("/database");}}};var k=class{constructor(e,t,s){this.builder=e;this.network=t;this.database=s;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:s}=this.network.state("offline");return e.status(t).json({message:s}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:s}=this.network.state("error");return e.status(t).json({message:s}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);t.status(200).json(i);}else t.status(400).json({message:"The entity is not exists"});}catch(s){t.status(500).send(s);}}}getTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),t.status(200).json(r.table.data)):t.status(400).json({message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}updateTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(...Array.isArray(i)?i:[i])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.body?.count||1,r=e.body?.strategy||"reset",a=e.params.name,i=this.builder.entities.get(a.toLowerCase());if(i){let{data:o}=await this.builder.build(i.type,{count:s});if(await i.table.read(),r==="once"&&i.table.data.length>0)return t.status(200).json({message:`${a} entity was seeded once before.`});await i.table.update(c=>{r!=="merge"&&(c.length=0);let l=Array.isArray(o)?o:[o];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){t.status(500).send(s);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.update(a=>a.length=0),t.status(200).json({success:!0})):t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){console.log({error:s}),t.status(500).send(s);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(i)),t.status(301).redirect(`/database/${s.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),r.table.data.length>0&&await r.table.update(a=>a.length=0),t.status(301).redirect(`/database/${s.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var V=u.readJSONSync(d.join(f,"../package.json")),A=class{constructor(e,t,s,r,a){this.router=e;this.config=t;this.network=s;this.database=r;this.serverCLIOptions=a;let{pathPrefix:i}=this.config.options.server(this.serverCLIOptions.pathPrefix,this.serverCLIOptions.port);this.prefix=i;}prefix;async register(){let e=await P(this.config,this.serverCLIOptions,this.database),t=new S(e,this.database,V),s=new k(e,this.network,this.database);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.db()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,s.entity()),this.router.get(`/${this.prefix}/database/:name`,s.getTable()),this.router.post(`/${this.prefix}/database/:name`,s.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,s.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var D=class n{constructor(e){this.config=e;this.options=this.config.options.network(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new n(e)}timeout(){let e=this.chance(this.options?.timeoutRate);return e&&p.debug("Network timeout..."),e}error(){let e=this.chance(this.options?.errorRate);return e&&p.debug("Network error..."),e}state(e){switch(e){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(p.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options?.offline??false}middleware(e,t,s){let r=this.options?.errorRate||0,a=this.options?.timeoutRate||0,i=this.options?.offline??false,o=`delay=${this.resolveDelay()},error=${r},timeout=${a},offline=${i}`;t.setHeader("X-Fakelab-Network",o),s();}resolveDelay(){let e=this.options?.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};var R=class n{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||u.rmSync(this.DATABASE_DIR,{force:true,recursive:true});}options;DATABASE_DIR=d.resolve(m,".fakelab/db");static register(e){return new n(e)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled())try{await u.ensureDir(this.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch{p.error("Could not create database.");}}async modifyGitignoreFile(e){try{let t=d.resolve(m,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
|
|
6
|
+
`).some(r=>r.trim()===e.trim()))return;await u.appendFile(t,`
|
|
7
|
+
${e}`);}catch{}}};var C=class n{constructor(e,t){this.serverCLIOptions=e;this.config=t;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(e,t){return new n(e,t)}async start(){let e=T(),t=T.Router(),s=X.createServer(e),r=D.initHandlers(this.config),a=R.register(this.config);this.setupApplication(e,r),this.setupTemplateEngine(e),await this.config.initializeRuntimeConfig(f,this.serverCLIOptions),await a.initialize(),await new A(t,this.config,r,a,this.serverCLIOptions).register(),e.use(t),this.run(s,a,this.serverCLIOptions);}setupApplication(e,t){e.disable("x-powered-by"),e.use(T.json()),e.use(K({methods:"GET"})),e.use(T.static(f+"/public")),e.use(this.xPoweredMiddleware),e.use(t.middleware);}setupTemplateEngine(e){e.set("views",d.join(f,"views")),e.set("view engine","ejs"),e.use(Y),e.set("layout","layouts/main");}listen(e,t){e.enabled()&&p.info("database: %s",e.DATABASE_DIR),p.info("server listening to http://localhost:%d",t),console.log(Z.textSync("FAKELAB"));}run(e,t,s){let{port:r}=this.config.options.server(s.pathPrefix,s.port);e.listen(r,"localhost",()=>this.listen(t,r));}xPoweredMiddleware(e,t,s){t.setHeader("x-powered-by","fakelab"),s();}loadLocalEnv(){}};async function N(){try{let e=await new te().resolve({files:["fakelab.config.ts"]});return e||(p.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{p.error("Could not load the config file."),process.exit(1);}}var w=class n{constructor(e,t){this.snapshotCLIOptions=e;this.config=t;this.capture=this.capture.bind(this);}TARGET_LANGUAGE="typescript";DEFAULT_TYPE_NAME="Fakelab";SNAPSHOT_DIR=d.resolve(m,".fakelab/snapshots");static _instance;static async init(e){let t=await N();return this._instance||(this._instance=new n(e,t)),this._instance}static async prepare(e){let t=await N(),s=this._instance||new n({},t),{enabled:r,sources:a}=t.options.snapshot();return r&&e.freshSnapshots&&await s.updateAll(a,true),s}async capture(e){let{enabled:t,sources:s}=this.config.options.snapshot();if(!t)return;await u.ensureDir(this.SNAPSHOT_DIR),await u.ensureFile(d.resolve(this.SNAPSHOT_DIR,"__schema.json"));let r=await this.readSnapshotSchema();if(await this.duplicateExists(r)&&(p.error("Snapshot source names must be unique."),process.exit(1)),!e)return this.snapshotCLIOptions.refresh?await this.refresh(s,this.snapshotCLIOptions.refresh,r):this.snapshotCLIOptions.delete?await this.delete(s,this.snapshotCLIOptions.delete,r):await this.updateAll(s);let a=this.suffix(r.sources);this.snapshotCLIOptions?.refresh&&p.warn("--refresh flag has no effect when used with url. Refresh skipped."),this.snapshotCLIOptions?.delete&&p.warn("--delete flag has no effect when used with url. Delete skipped.");let i=await this.fetch({url:e,name:this.snapshotCLIOptions?.source||a});await this.save({url:e,name:this.snapshotCLIOptions?.source||a},i,r),await this.modifyGitignoreFile(".fakelab/*");}async save(e,t,s){try{let r=s.sources.find(i=>i.url===e.url);if(r){p.warn("%s snapshot is already captured. Use \x1B[36mnpx fakelab snapshot --refresh %s\x1B[0m to update.",e.url,r.name);return}let a=this.snapshotName(e.url,!1);p.info("Capturing %s snapshot...",a),e.name||this.snapshotCLIOptions?.source||p.warn("Snapshot source name not found. Auto-generating a name."),await this.write(e.url,t,e.name||this.snapshotCLIOptions?.source),p.success("Snapshot \x1B[34m%s\x1B[0m captured successfully.",a);}catch(r){console.log({error:r});}}async refresh(e,t,s){let r=e.find(o=>o.name===t.trim());if(r||(r=(s.sources||[]).find(o=>o.name===t)),!r){p.warn("Snapshot source not found. Refresh skipped.");return}p.info("Refreshing \x1B[34m%s\x1B[0m snapshot source...",r.name);let a=d.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(a)){let o=await this.fetch(r);await u.writeFile(a,o);}else {let o=await this.fetch(r);await this.save(r,o,s);}p.success("Snapshot source \x1B[34m%s\x1B[0m refreshed successfully.",r.name);}async delete(e,t,s){let r=e.find(i=>i.name===t.trim());if(r||(r=(s.sources||[]).find(i=>i.name===t)),!r){p.warn("Snapshot source not found. Delete skipped.");return}p.info("Deleting \x1B[34m%s\x1B[0m snapshot source...",r.name);let a=d.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));await u.rm(a,{force:true}),await this.updateSnapshotSchema({url:r.url,delete:true}),p.success("Snapshot source \x1B[34m%s\x1B[0m deleted successfully.",r.name);}async updateAll(e,t=false){p.info(t?"Refreshing all snapshots...":"Updating all snapshots...");let s=await this.readSnapshotSchema();try{await Promise.all(e.map(async r=>{let a=d.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(a)){let o=await this.fetch(r);await u.writeFile(a,o);}else {let o=await this.fetch(r);await this.save(r,o,s);}})),p.success("All snapshots are updated.");}catch(r){p.error("Failed to update.",r),process.exit(1);}}async fetch({name:e,url:t,headers:s}){this.isValidUrl(t)||(p.error("Invalid snapshot URL. Please provide a valid http URL."),process.exit(1));let a=await(await fetch(t,{headers:s})).text();this.isValidJSON(a)||(p.error("Invalid snapshot response format. Expected JSON but received non-JSON data."),process.exit(1));let i=await import('quicktype-core'),o=i.jsonInputForTargetLanguage(this.TARGET_LANGUAGE);await o.addSource({name:e,samples:[a]});let c=new i.InputData;c.addInput(o);let{lines:l}=await i.quicktype({inputData:c,lang:this.TARGET_LANGUAGE,rendererOptions:{"just-types":true}});return l.join(`
|
|
8
|
+
`)}async write(e,t,s,r){await u.writeFile(d.resolve(this.SNAPSHOT_DIR,this.snapshotName(e)),t),await this.updateSnapshotSchema({url:e,name:s,headers:r});}async readSnapshotSchema(){let e={sources:[]};try{e=await u.readJSON(d.resolve(this.SNAPSHOT_DIR,"__schema.json"));}catch{}return e.sources||(e.sources=[]),e}async updateSnapshotSchema(e){let t=await this.readSnapshotSchema(),s=t.sources||[],r=s.findIndex(a=>a.url===e.url);if(e.delete)s.splice(r,1);else if(r===-1)s.push({...e,name:e.name||this.suffix(s)});else {let a=s[r].name,i=s[r].headers;s.splice(r,1,{...s[r],name:e.name||a,headers:e.headers||i});}t.sources=s,await u.writeJSON(d.resolve(this.SNAPSHOT_DIR,"__schema.json"),t);}snapshotName(e,t=true){return e.replace(/^https?:\/\//,"").replace(/[\/:?.&=#]/g,"_")+(t?".ts":"")}suffix(e){let t=e.filter(s=>s.name.startsWith(this.DEFAULT_TYPE_NAME));return `${this.DEFAULT_TYPE_NAME}${t.length}`}isValidUrl(e){try{return new URL(e),!0}catch{return false}}isValidJSON(e){try{return JSON.parse(e),!0}catch{return false}}async duplicateExists(e){let t=e.sources.map(s=>s.name);return new Set(t).size!==t.length}async modifyGitignoreFile(e){try{let t=d.resolve(m,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
|
|
9
|
+
`).some(r=>r.trim()===e.trim()))return;await u.appendFile(t,`
|
|
10
|
+
${e}`);}catch{}}};var E=new Command,O=u.readJSONSync(d.join(f,"../package.json"));E.name(O.name).description(O.description).version(O.version);E.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").option("-f, --fresh-snapshots","capture or refresh all snapshots").action(async n=>{let e=await w.prepare(n);C.init(n,e.config).start();});E.command("snapshot").description("capture a url response to a fakelab entity").argument("[string]","url to capture").option("-s, --source <string>","specify snapshot source name").option("-r, --refresh <string>","refresh the specified snapshot").option("-d, --delete <string>","delete the specified snapshot").action(async(n,e)=>{(await w.init(e)).capture(n);});E.parse();
|
package/lib/main.d.ts
CHANGED
|
@@ -32,25 +32,6 @@ type DatabaseOptions = {
|
|
|
32
32
|
*/
|
|
33
33
|
enabled: boolean;
|
|
34
34
|
};
|
|
35
|
-
type BrowserExposeOptions = {
|
|
36
|
-
/**
|
|
37
|
-
* Name of the exposed object or module in the browser.
|
|
38
|
-
* @example `fakelab`
|
|
39
|
-
*/
|
|
40
|
-
name: string;
|
|
41
|
-
/**
|
|
42
|
-
* Exposure mode in the browser environment.
|
|
43
|
-
* - `"module"`: Exposed as an ES module
|
|
44
|
-
* - `"global"`: Attached to the global window object
|
|
45
|
-
*/
|
|
46
|
-
mode: "module" | "global";
|
|
47
|
-
};
|
|
48
|
-
type BrowserOptions = {
|
|
49
|
-
/**
|
|
50
|
-
* Controls how the runtime API is exposed in the browser.
|
|
51
|
-
*/
|
|
52
|
-
expose?: Partial<BrowserExposeOptions>;
|
|
53
|
-
};
|
|
54
35
|
type NetworkBehaviourOptions = {
|
|
55
36
|
/**
|
|
56
37
|
* Artificial response delay in milliseconds.
|
|
@@ -84,11 +65,21 @@ type NetworkOptions = NetworkBehaviourOptions & {
|
|
|
84
65
|
*/
|
|
85
66
|
presets?: Record<string, NetworkBehaviourOptions>;
|
|
86
67
|
};
|
|
68
|
+
type SourceHeaders = NonNullable<Parameters<typeof fetch>[1]>["headers"];
|
|
69
|
+
type SnapshotDataSource = {
|
|
70
|
+
url: string;
|
|
71
|
+
name: string;
|
|
72
|
+
headers?: SourceHeaders;
|
|
73
|
+
};
|
|
87
74
|
type SnapshotOptions = {
|
|
88
75
|
/**
|
|
89
|
-
* Enables snapshot
|
|
76
|
+
* Enables snapshot.
|
|
90
77
|
*/
|
|
91
78
|
enabled: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Predefined snapshot sources
|
|
81
|
+
*/
|
|
82
|
+
sources?: SnapshotDataSource[];
|
|
92
83
|
};
|
|
93
84
|
type ConfigOptions = {
|
|
94
85
|
/**
|
|
@@ -110,11 +101,6 @@ type ConfigOptions = {
|
|
|
110
101
|
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/database-mode|Database Documentation}
|
|
111
102
|
*/
|
|
112
103
|
database?: DatabaseOptions;
|
|
113
|
-
/**
|
|
114
|
-
* Browser runtime exposure options.
|
|
115
|
-
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/runtime-api|Browser Documentation}
|
|
116
|
-
*/
|
|
117
|
-
browser?: BrowserOptions;
|
|
118
104
|
/**
|
|
119
105
|
* Network simulation configuration.
|
|
120
106
|
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/network-simulation|Network Documentation}
|
|
@@ -131,31 +117,29 @@ type ServerCLIOptions = {
|
|
|
131
117
|
pathPrefix?: string;
|
|
132
118
|
port?: number;
|
|
133
119
|
locale?: string;
|
|
120
|
+
freshSnapshots?: boolean;
|
|
134
121
|
};
|
|
135
122
|
|
|
136
123
|
declare class Config {
|
|
137
124
|
private readonly configOptions;
|
|
138
125
|
readonly RUNTIME_SOURCE_FILENAME = "runtime.js";
|
|
139
|
-
readonly RUNTIME_DECL_FILENAME = "runtime.d.ts";
|
|
140
126
|
readonly FAKELAB_PERSIST_DIR = ".fakelab";
|
|
141
127
|
NETWORK_DEFAULT_OPTIONS: Readonly<NetworkOptions>;
|
|
142
128
|
constructor(configOptions: ConfigOptions);
|
|
143
129
|
get options(): {
|
|
144
130
|
server: (prefix?: string, port?: number) => Required<ServerOptions>;
|
|
145
|
-
browser: (name?: string, mode?: "module" | "global") => Required<BrowserOptions>;
|
|
146
131
|
database: () => Required<DatabaseOptions>;
|
|
147
132
|
network: () => NetworkOptions;
|
|
148
133
|
snapshot: () => Required<SnapshotOptions>;
|
|
149
134
|
faker: (locale?: FakerLocale) => Required<FakerEngineOptions>;
|
|
150
135
|
};
|
|
151
136
|
private _serverOptions;
|
|
152
|
-
private _browserOptions;
|
|
153
137
|
private _databaseOptions;
|
|
154
138
|
private _networkOptions;
|
|
155
139
|
private _snapshotOptions;
|
|
156
140
|
private _fakerOptions;
|
|
157
141
|
files(_sourcePath?: string): Promise<string[]>;
|
|
158
|
-
|
|
142
|
+
initializeRuntimeConfig(dirname: string, options: ServerCLIOptions): Promise<void>;
|
|
159
143
|
private getSnapshotSourceFiles;
|
|
160
144
|
private tryStat;
|
|
161
145
|
private isReadable;
|
|
@@ -165,4 +149,4 @@ declare class Config {
|
|
|
165
149
|
|
|
166
150
|
declare function defineConfig(options: ConfigOptions): Config;
|
|
167
151
|
|
|
168
|
-
export { defineConfig };
|
|
152
|
+
export { type ConfigOptions, defineConfig };
|
package/lib/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import u from'fast-glob';import i from'path';import _ from'fs-extra';import {stat,access,constants}from'fs/promises';import m from'is-glob';import a from'picocolors';import {transform}from'esbuild';import {fileURLToPath}from'url';var g=new Intl.ListFormat("en",{style:"long",type:"unit"}),s=class{static label(t){switch(t){case "info":return a.blueBright(t.toUpperCase());case "error":return a.redBright(t.toUpperCase());case "warn":return a.yellowBright(t.toUpperCase());case "success":return a.greenBright(t.toUpperCase())}}static log(t,e){return [a.dim(`[${new Date().toISOString()}]`),this.label(t),e].join(" ")}static info(t,...e){console.log(this.log("info",t),...e);}static warn(t,...e){console.log(this.log("warn",t),...e);}static error(t,...e){console.error(this.log("error",t),...e);}static success(t,...e){console.log(this.log("success",t),...e);}static debug(t,...e){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",t),...e);}static list(t){return g.format(t)}};var f=["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"];function h(){let n=Intl.DateTimeFormat().resolvedOptions().locale;if(!n)return "en";let[t]=n.split("-"),e=t.toLowerCase();return f.includes(e)?e:"en"}var d=`let fl = {};
|
|
2
2
|
let db = {};
|
|
3
3
|
fl.url = () => "http://localhost:PORT/PREFIX/";
|
|
4
4
|
fl.fetch = async function (name, count) {
|
|
@@ -13,170 +13,41 @@ fl.fetch = async function (name, count) {
|
|
|
13
13
|
return result;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
db.enabled = () =>
|
|
16
|
+
db.enabled = () => ENABLED;
|
|
17
17
|
|
|
18
|
-
db.type$ = function (
|
|
18
|
+
db.type$ = function (_name) {};
|
|
19
19
|
|
|
20
20
|
db.get = async function (name, predicate) {
|
|
21
|
-
const response = await fetch(
|
|
21
|
+
const response = await fetch(fakelab.url() + "database/" + name);
|
|
22
22
|
|
|
23
23
|
if (!response.ok) throw new Error("[fakelab] Failed to retreived data from database.");
|
|
24
24
|
|
|
25
25
|
const result = await response.json();
|
|
26
26
|
|
|
27
|
-
if(!Array.isArray(result)) throw new Error("[fakelab] Database table data must be an array.");
|
|
27
|
+
if (!Array.isArray(result)) throw new Error("[fakelab] Database table data must be an array.");
|
|
28
28
|
|
|
29
|
-
if (typeof predicate ===
|
|
29
|
+
if (typeof predicate === "function") return result.find(predicate) ?? null;
|
|
30
30
|
|
|
31
31
|
return result;
|
|
32
32
|
};
|
|
33
33
|
db.post = async function (name) {
|
|
34
|
-
const response = await fetch(
|
|
34
|
+
const response = await fetch(fakelab.url() + "database/" + name, { method: "POST", headers: { "Content-Type": "application/json" } });
|
|
35
35
|
|
|
36
36
|
if (!response.ok) throw new Error("[fakelab] Failed to post data to database.");
|
|
37
37
|
};
|
|
38
38
|
db.seed = async function (name, options) {
|
|
39
|
-
const response = await fetch(
|
|
39
|
+
const response = await fetch(fakelab.url() + "database/insert/" + name, { method: "POST", body: JSON.stringify(options), headers: { "Content-Type": "application/json" } });
|
|
40
40
|
|
|
41
41
|
if (!response.ok) throw new Error("[fakelab] Failed to seed data to database.");
|
|
42
42
|
};
|
|
43
43
|
db.flush = async function (name) {
|
|
44
|
-
const response = await fetch(
|
|
44
|
+
const response = await fetch(fakelab.url() + "database/flush/" + name, { method: "POST" });
|
|
45
45
|
|
|
46
46
|
if (!response.ok) throw new Error("[fakelab] Failed to flush seeded data from database.");
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const fakelab = Object.freeze(fl);
|
|
50
50
|
const database = Object.freeze(db);
|
|
51
51
|
|
|
52
|
-
export {
|
|
53
|
-
|
|
54
|
-
declare function get<T extends keyof Runtime$>(name: T): Promise<Array<Runtime$[T]>>;
|
|
55
|
-
declare function get<T extends keyof Runtime$>(name: T, predicate: (value: Runtime$[T]) => boolean): Promise<Runtime$[T] | null>;
|
|
56
|
-
declare function post<T extends keyof Runtime$>(name: T): Promise<void>;
|
|
57
|
-
declare function seed<T extends keyof Runtime$>(name: T, options?: SeedOptions): Promise<void>;
|
|
58
|
-
declare function flush<T extends keyof Runtime$>(name: T): Promise<void>;
|
|
59
|
-
declare function enabled(): boolean;
|
|
60
|
-
declare function url(): string;
|
|
61
|
-
declare const NAME: {
|
|
62
|
-
fetch: typeof fetch;
|
|
63
|
-
url: typeof url;
|
|
64
|
-
};
|
|
65
|
-
declare const database: {
|
|
66
|
-
get: typeof get;
|
|
67
|
-
post: typeof post;
|
|
68
|
-
seed: typeof seed;
|
|
69
|
-
flush: typeof flush;
|
|
70
|
-
enabled: typeof enabled;
|
|
71
|
-
};
|
|
72
|
-
type SeedOptions = {
|
|
73
|
-
/**
|
|
74
|
-
* Number of records to generate.
|
|
75
|
-
*/
|
|
76
|
-
count?: number;
|
|
77
|
-
/**
|
|
78
|
-
* Defines how seeding interacts with existing database data.
|
|
79
|
-
* - \`"reset"\`: Removes all existing data and recreates it from scratch.
|
|
80
|
-
* - \`"once"\`: Seeds data only if the database is empty.
|
|
81
|
-
* - \`"merge"\`: Inserts new records and updates existing ones. The total number of items per table
|
|
82
|
-
* is limited to \`1000\` records.
|
|
83
|
-
* @default "reset"
|
|
84
|
-
*/
|
|
85
|
-
strategy?: "reset" | "once" | "merge"
|
|
86
|
-
}
|
|
87
|
-
type Result$<T, CT> = CT extends number ? (CT extends 0 ? T : T[]) : T;
|
|
88
|
-
interface Runtime$ {}
|
|
89
|
-
|
|
90
|
-
type Typeof<T extends keyof Runtime$> = ReturnType<typeof type$<T>>
|
|
91
|
-
|
|
92
|
-
export type { Typeof }
|
|
93
|
-
export { NAME, database };`,T=`global.NAME = {};
|
|
94
|
-
global.NAME.database = {};
|
|
95
|
-
global.NAME.url = () => "http://localhost:PORT/PREFIX/";
|
|
96
|
-
global.NAME.fetch = async function (name, count) {
|
|
97
|
-
const search = count ? "?count=" + count : "";
|
|
98
|
-
|
|
99
|
-
const response = await fetch(global.NAME.url() + name + search);
|
|
100
|
-
|
|
101
|
-
if (!response.ok) throw new Error("[fakelab] Failed to retreived mock data.");
|
|
102
|
-
|
|
103
|
-
const result = await response.json();
|
|
104
|
-
|
|
105
|
-
return result;
|
|
106
|
-
};
|
|
107
|
-
global.NAME.database.enabled = () => ENABLED_COND;
|
|
108
|
-
global.NAME.database.get = async function (name, predicate) {
|
|
109
|
-
const response = await fetch(global.NAME.url() + "database/" + name);
|
|
110
|
-
|
|
111
|
-
if (!response.ok) throw new Error("[fakelab] Failed to retreived data from database.");
|
|
112
|
-
|
|
113
|
-
const result = await response.json();
|
|
114
|
-
|
|
115
|
-
if(!Array.isArray(result)) throw new Error("[fakelab] Database table data must be an array.");
|
|
116
|
-
|
|
117
|
-
if (typeof predicate === 'function') return result.find(predicate) ?? null;
|
|
118
|
-
|
|
119
|
-
return result;
|
|
120
|
-
};
|
|
121
|
-
global.NAME.database.post = async function (name) {
|
|
122
|
-
const response = await fetch(global.NAME.url() + "database/" + name, { method: "POST", headers: {"Content-Type": "application/json" } });
|
|
123
|
-
|
|
124
|
-
if (!response.ok) throw new Error("[fakelab] Failed to post data to database.");
|
|
125
|
-
};
|
|
126
|
-
global.NAME.database.seed = async function (name, options) {
|
|
127
|
-
const response = await fetch(global.NAME.url() + "database/insert/" + name, { method: "POST", body: JSON.stringify(options), headers: {"Content-Type": "application/json" } });
|
|
128
|
-
|
|
129
|
-
if (!response.ok) throw new Error("[fakelab] Failed to seed data to database.");
|
|
130
|
-
};
|
|
131
|
-
global.NAME.database.flush = async function (name) {
|
|
132
|
-
const response = await fetch(global.NAME.url() + "database/flush/" + name, { method: "POST" });
|
|
133
|
-
|
|
134
|
-
if (!response.ok) throw new Error("[fakelab] Failed to flush seeded data from database.");
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const NAME = Object.freeze(fl);
|
|
138
|
-
const database = Object.freeze(db);
|
|
139
|
-
|
|
140
|
-
export { NAME, database };`,A=`export {};
|
|
141
|
-
|
|
142
|
-
declare global {
|
|
143
|
-
const database: {
|
|
144
|
-
get<T extends keyof Runtime$>(name: T): Promise<Array<Runtime$[T]>>;
|
|
145
|
-
get<T extends keyof Runtime$>(name: T, predicate: (value: Runtime$[T]) => boolean): Promise<Runtime$[T] | null>;
|
|
146
|
-
post(name: keyof Runtime$): Promise<void>;
|
|
147
|
-
seed(name: keyof Runtime$, options?: SeedOptions): Promise<void>;
|
|
148
|
-
flush(name: keyof Runtime$): Promise<void>;
|
|
149
|
-
enabled(): boolean;
|
|
150
|
-
};
|
|
151
|
-
const NAME: {
|
|
152
|
-
fetch<T extends keyof Runtime$, C extends number | undefined = undefined>(name: T, count?: C): Promise<Result$<Runtime$[T], C>>;
|
|
153
|
-
url(): string;
|
|
154
|
-
database: typeof database;
|
|
155
|
-
};
|
|
156
|
-
type SeedOptions = {
|
|
157
|
-
/**
|
|
158
|
-
* Number of records to generate.
|
|
159
|
-
*/
|
|
160
|
-
count?: number;
|
|
161
|
-
/**
|
|
162
|
-
* Defines how seeding interacts with existing database data.
|
|
163
|
-
* - \`"reset"\`: Removes all existing data and recreates it from scratch.
|
|
164
|
-
* - \`"once"\`: Seeds data only if the database is empty.
|
|
165
|
-
* - \`"merge"\`: Inserts new records and updates existing ones. The total number of items per table
|
|
166
|
-
* is limited to \`1000\` records.
|
|
167
|
-
* @default "reset"
|
|
168
|
-
*/
|
|
169
|
-
strategy?: "reset" | "once" | "merge"
|
|
170
|
-
}
|
|
171
|
-
type Result$<T, CT> = CT extends number ? (CT extends 0 ? T : T[]) : T;
|
|
172
|
-
interface Runtime$ {}
|
|
173
|
-
interface Window {
|
|
174
|
-
readonly NAME: typeof NAME;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
namespace NodeJS {
|
|
178
|
-
interface Global {
|
|
179
|
-
NAME: typeof NAME;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}`;var h=class{constructor(e,t,r,o){this.port=e;this.prefix=t;this.browserOptions=r;this.dbOptions=o;this.name=this.browserOptions?.expose?.name||"fakelab",this.databaseEnabled=this.dbOptions?.enabled??true?"true":"false";}name;databaseEnabled;transformOptions={minify:true,platform:"browser",target:"es2022"};replacer(e,t){let r=e;for(let o in t)r=r.replace(new RegExp(o,"g"),t[o].toString().trim());return r}async prepareGlobalSource(){let e=this.replacer(T,{NAME:this.name,PORT:this.port,PREFIX:this.prefix,ENABLED_COND:this.databaseEnabled});try{let{code:t}=await transform(e,this.transformOptions);return t}catch(t){return t instanceof Error&&s.warn(t.message),e}}globalDeclaration(){return this.replacer(A,{NAME:this.name})}},d=class n extends h{constructor(t,r,o,p){super(t,r,o,p);this.port=t;this.prefix=r;this.browserOptions=o;this.dbOptions=p;}static init(t,r,o,p){let u=new n(t,r,o,p);return new Proxy(u,{get(a,l){return l==="prepareSource"?o?.expose?.mode==="global"?a.prepareGlobalSource:a.prepareSource:l==="declaration"?o?.expose?.mode==="global"?a.globalDeclaration:a.declaration:a[l]}})}async prepareSource(){let t=this.replacer(y,{NAME:this.name,PORT:this.port,PREFIX:this.prefix,ENABLED_COND:this.databaseEnabled});try{let{code:r}=await transform(t,this.transformOptions);return r}catch(r){return r instanceof Error&&s.warn(r.message),t}}declaration(){return this.replacer(w,{NAME:this.name})}};i.dirname(fileURLToPath(import.meta.url));var k=process.cwd();var f=class{constructor(e){this.configOptions=e;this.files=this.files.bind(this),this._serverOptions=this._serverOptions.bind(this),this._browserOptions=this._browserOptions.bind(this),this._databaseOptions=this._databaseOptions.bind(this),this._networkOptions=this._networkOptions.bind(this),this._snapshotOptions=this._snapshotOptions.bind(this),this._fakerOptions=this._fakerOptions.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.configOptions.network?.delay||0,errorRate:this.configOptions.network?.errorRate||0,timeoutRate:this.configOptions.network?.timeoutRate||0,offline:this.configOptions.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";RUNTIME_DECL_FILENAME="runtime.d.ts";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;get options(){return {server:this._serverOptions,browser:this._browserOptions,database:this._databaseOptions,network:this._networkOptions,snapshot:this._snapshotOptions,faker:this._fakerOptions}}_serverOptions(e,t){return {pathPrefix:e||this.configOptions.server?.pathPrefix||"api",port:t||this.configOptions.server?.port||5200,includeSnapshots:this.configOptions.server?.includeSnapshots??true}}_browserOptions(e,t){return {expose:{mode:t||this.configOptions.browser?.expose?.mode||g,name:e||this.configOptions.browser?.expose?.name||E}}}_databaseOptions(){return {enabled:this.configOptions.database?.enabled??false}}_networkOptions(){let e=this.configOptions.network?.preset,t=this.configOptions.network?.presets??{};return !e||!t[e]?this.NETWORK_DEFAULT_OPTIONS:{...t[e],...this.configOptions.network??{}}}_snapshotOptions(){return {enabled:this.configOptions.snapshot?.enabled??false}}_fakerOptions(e){let t=(e||this.configOptions.faker?.locale)?.toLowerCase();return t&&m.includes(t)?{locale:t}:{locale:O()}}async files(e){let t=this.resolveSourcePath(e||this.configOptions.sourcePath),r=Array.from(new Set((await Promise.all(t.map(o=>this.resolveTSFiles(o)))).flat()));if(this._serverOptions().includeSnapshots){let o=await this.getSnapshotSourceFiles();r.push(...o);}return r.length===0&&(s.error("No Typescript files found in: %s",s.list(t.map(o=>i.basename(o)))),process.exit(1)),r}async generateInFileRuntimeConfig(e,t){let{port:r,pathPrefix:o}=this._serverOptions(t.pathPrefix,t.port),p=i.resolve(e,this.RUNTIME_SOURCE_FILENAME),u=i.resolve(e,this.RUNTIME_DECL_FILENAME),a=d.init(r,o,this.configOptions.browser,this.configOptions.database),l=await a.prepareSource();await Promise.all([_.writeFile(p,l),_.writeFile(u,a.declaration())]);}async getSnapshotSourceFiles(){let e=await b(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:k});return e.length>0&&s.info("snapshot(s): %s",s.list(e.map(t=>i.parse(t).name))),e}async tryStat(e){try{return await stat(e)}catch{return null}}async isReadable(e){try{return await access(e,constants.R_OK),!0}catch{return false}}resolveSourcePath(e){return (Array.isArray(e)?e:[e]).map(r=>L(r,{strict:true})?r:i.resolve(r))}async resolveTSFiles(e){if(L(e,{strict:true}))return s.info("source: %s",e),b(e,{absolute:true,ignore:["**/*.d.ts"]});let t=i.resolve(e),r=t.endsWith(".ts")?t:t+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("source: %s",r),[r]):(await this.tryStat(t))?.isDirectory()?(s.info("source: %s",t),b("**/*.ts",{cwd:t,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",i.basename(r)),[])}};function D(n){return new f(n)}export{D as defineConfig};
|
|
52
|
+
export { fakelab, database };
|
|
53
|
+
`;var c=class n{constructor(t,e,r){this.port=t;this.prefix=e;this.dbOptions=r;this.enabled=this.dbOptions?.enabled??true?"true":"false";}enabled;transformOptions={minify:true,platform:"browser",target:"es2022"};static init(t,e,r){return new n(t,e,r)}replacer(t,e){let r=t;for(let o in e)r=r.replace(new RegExp(o,"g"),e[o].toString().trim());return r}async prepareSource(){let t=this.replacer(d,{PORT:this.port,PREFIX:this.prefix,ENABLED:this.enabled});try{let{code:e}=await transform(t,this.transformOptions);return e}catch(e){return e instanceof Error&&s.warn(e.message),t}}};i.dirname(fileURLToPath(import.meta.url));var O=process.cwd();var l=class{constructor(t){this.configOptions=t;this.files=this.files.bind(this),this._serverOptions=this._serverOptions.bind(this),this._databaseOptions=this._databaseOptions.bind(this),this._networkOptions=this._networkOptions.bind(this),this._snapshotOptions=this._snapshotOptions.bind(this),this._fakerOptions=this._fakerOptions.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.configOptions.network?.delay||0,errorRate:this.configOptions.network?.errorRate||0,timeoutRate:this.configOptions.network?.timeoutRate||0,offline:this.configOptions.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;get options(){return {server:this._serverOptions,database:this._databaseOptions,network:this._networkOptions,snapshot:this._snapshotOptions,faker:this._fakerOptions}}_serverOptions(t,e){return {pathPrefix:t||this.configOptions.server?.pathPrefix||"api",port:e||this.configOptions.server?.port||5200,includeSnapshots:this.configOptions.server?.includeSnapshots??true}}_databaseOptions(){return {enabled:this.configOptions.database?.enabled??false}}_networkOptions(){let t=this.configOptions.network?.preset,e=this.configOptions.network?.presets??{};return !t||!e[t]?this.NETWORK_DEFAULT_OPTIONS:{...e[t],...this.configOptions.network??{}}}_snapshotOptions(){return {enabled:this.configOptions.snapshot?.enabled??false,sources:this.configOptions.snapshot?.sources||[]}}_fakerOptions(t){let e=(t||this.configOptions.faker?.locale)?.toLowerCase();return e&&f.includes(e)?{locale:e}:{locale:h()}}async files(t){let e=this.resolveSourcePath(t||this.configOptions.sourcePath),r=Array.from(new Set((await Promise.all(e.map(o=>this.resolveTSFiles(o)))).flat()));if(this._serverOptions().includeSnapshots){let o=await this.getSnapshotSourceFiles();r.push(...o);}if(r.length===0){let o=e.map(p=>i.basename(p));o.length===0?s.error("No source path found."):s.error("No Typescript files found in: %s",s.list(o)),process.exit(1);}return r}async initializeRuntimeConfig(t,e){let{port:r,pathPrefix:o}=this._serverOptions(e.pathPrefix,e.port),p=i.resolve(t,this.RUNTIME_SOURCE_FILENAME),b=await c.init(r,o,this.configOptions.database).prepareSource();await _.writeFile(p,b);}async getSnapshotSourceFiles(){let t=await u(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:O});return t.length>0&&s.info("snapshot(s): %s",s.list(t.map(e=>i.parse(e).name))),t}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(r=>m(r,{strict:true})?r:i.resolve(r))}async resolveTSFiles(t){if(m(t,{strict:true}))return s.info("source: %s",t),u(t,{absolute:true,ignore:["**/*.d.ts"]});let e=i.resolve(t),r=e.endsWith(".ts")?e:e+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("source: %s",r),[r]):(await this.tryStat(e))?.isDirectory()?(s.info("source: %s",e),u("**/*.ts",{cwd:e,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",i.basename(r)),[])}};function R(n){return new l(n)}export{R as defineConfig};
|
package/lib/public/css/style.css
CHANGED
package/lib/runtime.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as C__Users_P_S_Desktop_projects_digimock_types_user_ts from '../C:/Users/P.S/Desktop/projects/digimock/types/user.ts';
|
|
2
|
+
|
|
3
|
+
declare function fetch<T extends keyof Runtime$, C extends number | undefined = undefined>(name: T, count?: C): Promise<Result$<Runtime$[T], C>>;
|
|
4
|
+
declare function type$<T extends keyof Runtime$>(): Runtime$[T];
|
|
5
|
+
declare function get<T extends keyof Runtime$>(name: T): Promise<Array<Runtime$[T]>>;
|
|
6
|
+
declare function get<T extends keyof Runtime$>(name: T, predicate: (value: Runtime$[T]) => boolean): Promise<Runtime$[T] | null>;
|
|
7
|
+
declare function post<T extends keyof Runtime$>(name: T): Promise<void>;
|
|
8
|
+
declare function seed<T extends keyof Runtime$>(name: T, options?: SeedOptions): Promise<void>;
|
|
9
|
+
declare function flush<T extends keyof Runtime$>(name: T): Promise<void>;
|
|
10
|
+
declare function enabled(): boolean;
|
|
11
|
+
declare function url(): string;
|
|
12
|
+
declare const fakelab: {
|
|
13
|
+
fetch: typeof fetch;
|
|
14
|
+
url: typeof url;
|
|
15
|
+
};
|
|
16
|
+
declare const database: {
|
|
17
|
+
get: typeof get;
|
|
18
|
+
post: typeof post;
|
|
19
|
+
seed: typeof seed;
|
|
20
|
+
flush: typeof flush;
|
|
21
|
+
enabled: typeof enabled;
|
|
22
|
+
};
|
|
23
|
+
type SeedOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Number of records to generate.
|
|
26
|
+
*/
|
|
27
|
+
count?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Defines how seeding interacts with existing database data.
|
|
30
|
+
* - `"reset"`: Removes all existing data and recreates it from scratch.
|
|
31
|
+
* - `"once"`: Seeds data only if the database is empty.
|
|
32
|
+
* - `"merge"`: Inserts new records and updates existing ones. The total number of items per table
|
|
33
|
+
* is limited to `1000` records.
|
|
34
|
+
* @default "reset"
|
|
35
|
+
*/
|
|
36
|
+
strategy?: "reset" | "once" | "merge";
|
|
37
|
+
};
|
|
38
|
+
type Result$<T, CT> = CT extends number ? (CT extends 0 ? T : T[]) : T;
|
|
39
|
+
|
|
40
|
+
type Typeof<T extends keyof Runtime$> = ReturnType<typeof type$<T>>;
|
|
41
|
+
|
|
42
|
+
interface Runtime$ {}
|
|
43
|
+
|
|
44
|
+
interface Runtime$ {
|
|
45
|
+
User: C__Users_P_S_Desktop_projects_digimock_types_user_ts.User
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { type Typeof, database, fakelab };
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<% entities.forEach((entity,entityName) => { %>
|
|
18
18
|
<a class="sidebar_content_list_item <%= entityName === name ? 'active' : '' %>" href="/entities/<%= entityName %>">
|
|
19
19
|
<span class="item_name"><%= entityName %></span>
|
|
20
|
-
<span class="item_filepath"><%= entity.filepath %></span>
|
|
20
|
+
<span class="item_filepath"><%= entity.snapshot ? "(snapshot)" : entity.filepath %></span>
|
|
21
21
|
</a>
|
|
22
22
|
<% }) %>
|
|
23
23
|
</div>
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fakelab",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"description": "A easy-config mock server for frontend developers.",
|
|
6
|
+
"description": "A fast, easy-config mock server for frontend developers.",
|
|
7
7
|
"main": "lib/main.js",
|
|
8
8
|
"module": "lib/main.js",
|
|
9
9
|
"types": "lib/main.d.ts",
|