mock-api-studio-cli 0.1.2
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/LICENSE +28 -0
- package/README.md +128 -0
- package/dist/index.js +116 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Mock API Studio CLI Commercial License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mock API Studio. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Subject to the Mock API Studio Terms of Service available at
|
|
6
|
+
https://mockapistudio.dev/terms, Mock API Studio grants an authorized account
|
|
7
|
+
holder a limited, non-exclusive, non-transferable, and revocable license to
|
|
8
|
+
install and use this software solely with the Mock API Studio service for the
|
|
9
|
+
account holder's internal development, testing, prototyping, and demonstration
|
|
10
|
+
work.
|
|
11
|
+
|
|
12
|
+
You may not copy, modify, publish, distribute, sublicense, sell, rent, lease,
|
|
13
|
+
make available as a competing product or service, or circumvent subscription,
|
|
14
|
+
authentication, authorization, usage, or technical controls in this software.
|
|
15
|
+
You may not reverse engineer, decompile, or disassemble this software except to
|
|
16
|
+
the limited extent that applicable law expressly permits despite this
|
|
17
|
+
restriction.
|
|
18
|
+
|
|
19
|
+
This license ends when your authorization to use the Mock API Studio service
|
|
20
|
+
ends. On termination, you must stop using and delete copies of this software,
|
|
21
|
+
except where retention is required by applicable law.
|
|
22
|
+
|
|
23
|
+
Third-party software included with or required by this software remains subject
|
|
24
|
+
to its respective license terms.
|
|
25
|
+
|
|
26
|
+
The Mock API Studio Terms of Service govern disclaimers, limitations of
|
|
27
|
+
liability, termination, and all other terms relating to this software and the
|
|
28
|
+
service.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Mock API Studio
|
|
2
|
+
|
|
3
|
+
Start a local mock API server from your Mock API Studio cloud project definitions.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Run without installing globally:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx mock-api-studio-cli --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install the `mock-api-studio` command globally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install --global mock-api-studio-cli
|
|
17
|
+
mock-api-studio --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
Run the interactive project picker:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx mock-api-studio-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The first run asks for an API token, saves it locally, lists your projects, and starts the selected mock server.
|
|
29
|
+
|
|
30
|
+
Start a project directly by slug:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx mock-api-studio-cli my-project
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use an explicit port when you want to override the project default:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx mock-api-studio-cli my-project --port 4500
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The interactive picker first lets you choose between your personal workspace and
|
|
43
|
+
any active Team workspaces you joined. To start a Team project directly, pass its
|
|
44
|
+
workspace ID:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx mock-api-studio-cli start team-project --workspace 11111111-1111-4111-8111-111111111111
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Team viewers can discover, sync, and run projects locally, but cannot change
|
|
51
|
+
cloud project configuration. Suspended or revoked workspace access is reported
|
|
52
|
+
before a project is started.
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
### `start <slug>`
|
|
57
|
+
|
|
58
|
+
Starts a project without opening the interactive picker.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx mock-api-studio-cli start my-project
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use `--workspace <id>` when the slug belongs to a joined Team workspace.
|
|
65
|
+
|
|
66
|
+
### `whoami`
|
|
67
|
+
|
|
68
|
+
Shows the account associated with the saved token.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx mock-api-studio-cli whoami
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `logout`
|
|
75
|
+
|
|
76
|
+
Removes the locally saved token.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx mock-api-studio-cli logout
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Local Config
|
|
83
|
+
|
|
84
|
+
The interactive CLI stores the token in:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
~/.mock-api-studio/config.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Token resolution order:
|
|
91
|
+
|
|
92
|
+
1. `--token`
|
|
93
|
+
2. local config
|
|
94
|
+
3. `MOCK_API_TOKEN`
|
|
95
|
+
|
|
96
|
+
The CLI connects to `https://api.mockapistudio.dev` by default. For local backend
|
|
97
|
+
development, override it with `MOCK_API_BASE_URL` as shown in `.env.example`.
|
|
98
|
+
|
|
99
|
+
## Mock Server
|
|
100
|
+
|
|
101
|
+
The synced mock server exposes:
|
|
102
|
+
|
|
103
|
+
- configured resource routes such as `/users`
|
|
104
|
+
- only the enabled operations for each resource
|
|
105
|
+
- `GET /openapi.json`
|
|
106
|
+
- `GET /docs`
|
|
107
|
+
- runtime control routes under `/_mock`
|
|
108
|
+
|
|
109
|
+
## Troubleshooting
|
|
110
|
+
|
|
111
|
+
- `Port is already in use`
|
|
112
|
+
Use `--port <number>` with a free port, or change the project local mock port in the dashboard.
|
|
113
|
+
- `Invalid or expired API token`
|
|
114
|
+
Run `npx mock-api-studio-cli logout`, then run `npx mock-api-studio-cli` again and paste a fresh token.
|
|
115
|
+
- `Project has no mock resources`
|
|
116
|
+
Add mock resources in the dashboard, then rerun the CLI.
|
|
117
|
+
|
|
118
|
+
## Links
|
|
119
|
+
|
|
120
|
+
- [Mock API Studio](https://mockapistudio.dev)
|
|
121
|
+
- [Documentation](https://mockapistudio.dev/docs)
|
|
122
|
+
- [Contact and support](https://mockapistudio.dev/contact)
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
Mock API Studio CLI is commercial software. Use requires an authorized Mock API
|
|
127
|
+
Studio account and is governed by the included license and the
|
|
128
|
+
[Terms of Service](https://mockapistudio.dev/terms).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import"dotenv/config";import{realpathSync as sn}from"fs";import{resolve as _t,win32 as Ft}from"path";import{fileURLToPath as an}from"url";import{Command as cn}from"commander";import*as f from"@clack/prompts";async function Ie(e,t,r=fetch,n){let o=G(),s=new URLSearchParams({slug:t});n!==void 0&&s.set("workspaceId",n);let i=await r(`${o}/api/sync?${s.toString()}`,{method:"GET",headers:{"Content-Type":"application/json","X-Api-Token":e}});if(i.status===401)throw new Error("Invalid or expired API token. Check your MOCK_API_TOKEN environment variable.");if(i.status===403)throw new Error(await ie(i,"Workspace access is forbidden."));if(i.status===404)throw new Error(`No project found with slug "${t}". Check the slug in your dashboard.`);if(!i.ok)throw new Error(`Sync failed with status ${i.status}. Please try again.`);return await i.json()}async function ve(e,t=fetch,r){let n=G(),o=r===void 0?"":`?workspaceId=${encodeURIComponent(r)}`,s=await t(`${n}/api/projects/cli${o}`,{method:"GET",headers:{"Content-Type":"application/json","X-Api-Token":e}});if(s.status===401)throw new Error("TOKEN_INVALID");if(s.status===403)throw new Error(await ie(s,"WORKSPACE_ACCESS_FORBIDDEN"));if(!s.ok)throw new Error(`Failed to fetch projects with status ${s.status}.`);return(await s.json()).projects??[]}async function Ne(e,t=fetch){let r=G(),n=await t(`${r}/api/workspaces/cli`,{method:"GET",headers:{"Content-Type":"application/json","X-Api-Token":e}});if(n.status===401)throw new Error("TOKEN_INVALID");if(!n.ok)throw new Error(await ie(n,`Failed to fetch workspaces with status ${n.status}.`));return(await n.json()).workspaces??[]}async function q(e,t=fetch){let r=G(),n=await t(`${r}/api/auth/me`,{method:"GET",headers:{"Content-Type":"application/json","X-Api-Token":e}});if(n.status===401)throw new Error("TOKEN_INVALID");if(n.status===403)throw new Error("SUBSCRIPTION_INACTIVE");if(!n.ok)throw new Error(`Failed to fetch account info with status ${n.status}.`);let o=await n.json();if(typeof o.account?.email!="string")throw new Error("Account response did not include an email address.");return o.account.email}function G(){return(process.env.MOCK_API_BASE_URL??"https://api.mockapistudio.dev").replace(/\/$/,"")}async function ie(e,t){try{let r=await e.json();return typeof r.error?.message=="string"&&r.error.message.length>0?r.error.message:t}catch{return t}}import Y from"fs";import Qt from"os";import $e from"path";function O(){try{let e=Y.readFileSync(N(),"utf8"),t=JSON.parse(e);if(t===null||typeof t!="object")return null;let r=t;return typeof r.token!="string"||r.token.length===0?null:{email:typeof r.email=="string"?r.email:void 0,token:r.token}}catch{return null}}function K(e){Y.mkdirSync(De(),{recursive:!0}),Y.writeFileSync(N(),`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,{encoding:"utf8",mode:384})}function J(){try{Y.unlinkSync(N())}catch{}}function N(){return $e.join(De(),"config.json")}function De(){return process.env.MOCK_API_STUDIO_CONFIG_DIR??$e.join(Qt.homedir(),".mock-api-studio")}import*as se from"@clack/prompts";import Le from"bcryptjs";import{randomUUID as _e}from"crypto";var Z=class{constructor(t){this.db=t;Xt(t)}db;register({email:t,password:r,profile:n}){let o=_e(),s=Le.hashSync(r,10);return this.db.prepare(`
|
|
4
|
+
INSERT INTO _auth_users (id, email, password_hash, profile)
|
|
5
|
+
VALUES (?, ?, ?, ?)
|
|
6
|
+
`).run(o,t,s,JSON.stringify(n)),this.findUserById(o)}findUserByEmail(t){let r=this.db.prepare("SELECT id, email, profile, created_at AS createdAt FROM _auth_users WHERE lower(email) = ?").get(ce(t));return r===void 0?null:ae(r)}findUserById(t){let r=this.db.prepare("SELECT id, email, profile, created_at AS createdAt FROM _auth_users WHERE id = ?").get(t);return r===void 0?null:ae(r)}emailExists(t){return this.db.prepare("SELECT 1 FROM _auth_users WHERE lower(email) = ?").get(ce(t))!==void 0}validateCredentials(t,r){let n=this.db.prepare("SELECT id, email, password_hash AS passwordHash, profile, created_at AS createdAt FROM _auth_users WHERE lower(email) = ?").get(ce(t));return n===void 0||!Le.compareSync(r,n.passwordHash)?null:ae(n)}issueToken(t,r){let n=_e(),o=r===null?null:Gt(r);return this.db.prepare(`
|
|
7
|
+
INSERT INTO _auth_tokens (token, user_id, expires_at)
|
|
8
|
+
VALUES (?, ?, ?)
|
|
9
|
+
`).run(n,t,o),n}validateToken(t){let r=this.db.prepare(`
|
|
10
|
+
SELECT user_id AS userId, expires_at AS expiresAt
|
|
11
|
+
FROM _auth_tokens
|
|
12
|
+
WHERE token = ?
|
|
13
|
+
`).get(t);return r===void 0?null:r.expiresAt!==null&&new Date(r.expiresAt).getTime()<=Date.now()?(this.revokeToken(t),null):this.findUserById(r.userId)}revokeToken(t){this.db.prepare("DELETE FROM _auth_tokens WHERE token = ?").run(t)}};function Xt(e){e.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS _auth_users (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
email TEXT NOT NULL UNIQUE,
|
|
17
|
+
password_hash TEXT NOT NULL,
|
|
18
|
+
profile TEXT NOT NULL DEFAULT '{}',
|
|
19
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE TABLE IF NOT EXISTS _auth_tokens (
|
|
23
|
+
token TEXT PRIMARY KEY,
|
|
24
|
+
user_id TEXT NOT NULL REFERENCES _auth_users(id) ON DELETE CASCADE,
|
|
25
|
+
expires_at TEXT,
|
|
26
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
27
|
+
);
|
|
28
|
+
`)}function ae(e){return{...qt(e.profile),createdAt:e.createdAt,email:e.email,id:e.id}}function qt(e){try{let t=JSON.parse(e);return t!==null&&typeof t=="object"&&!Array.isArray(t)?t:{}}catch{return{}}}function ce(e){return e.trim().toLowerCase()}function Gt(e){let t=new Date,r=e.match(/^(\d+)(h|d)$/);if(r===null)return t.setHours(t.getHours()+24),t.toISOString();let n=Number(r[1]);return r[2]==="h"?t.setHours(t.getHours()+n):t.setDate(t.getDate()+n),t.toISOString()}import Yt from"better-sqlite3";import Fe from"fs";import de from"path";var Jt=".mock-api-studio";function ue(){return de.join(process.cwd(),Jt)}function Zt(e){return Fe.mkdirSync(ue(),{recursive:!0}),de.join(ue(),`${e}.db`)}function Ue(e){return Fe.existsSync(de.join(ue(),`${e}.db`))}function Ke(e){let t=new Yt(Zt(e));return t.pragma("journal_mode = WAL"),t.pragma("foreign_keys = ON"),t}import er from"crypto";function ze(e){e.exec(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS _meta (
|
|
30
|
+
key TEXT PRIMARY KEY,
|
|
31
|
+
value TEXT NOT NULL
|
|
32
|
+
)
|
|
33
|
+
`)}function Ve(e,t,r=[]){let n=e.map(i=>JSON.stringify({operations:i.operations??[],path:i.path,settings:{pagination:{defaultLimit:i.settings?.pagination?.defaultLimit??null,enabled:i.settings?.pagination?.enabled??null,maxLimit:i.settings?.pagination?.maxLimit??null},recordCount:i.settings?.recordCount??null},sortableFields:i.sortableFields??[],source:i.source})).sort().join("|"),o=t===void 0?"":JSON.stringify({recordCountPerResource:t.recordCountPerResource,seed:t.seed??null}),s=r.map(i=>JSON.stringify({childEntity:i.childEntity,childForeignKey:i.childForeignKey,parentEntity:i.parentEntity,parentKey:i.parentKey,relationship:i.relationship})).sort().join("|");return er.createHash("sha256").update(`${n}|${o}|${s}`).digest("hex").slice(0,16)}function He(e){return e.prepare("SELECT value FROM _meta WHERE key = 'schema_hash'").get()?.value??null}function We(e,t){e.prepare(`
|
|
34
|
+
INSERT INTO _meta (key, value) VALUES ('schema_hash', ?)
|
|
35
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
36
|
+
`).run(t)}function Be(e){return e.prepare("SELECT value FROM _meta WHERE key = 'reset_requested'").get()?.value==="1"}function Qe(e){e.prepare(`
|
|
37
|
+
INSERT INTO _meta (key, value) VALUES ('reset_requested', '0')
|
|
38
|
+
ON CONFLICT(key) DO UPDATE SET value = '0'
|
|
39
|
+
`).run()}import le from"crypto";var j=["get","post","put","patch","delete"],z=class{resources=new Map;routeMap=new Map;registerResource(t,r,n,o=j,s={},i=[]){let a=new Map,c=rr(n,t.resourceName),u=c,d={...t,responseKind:t.responseKind??"collection"};for(let g of r){let R=tr(g);a.set(R,{...g,id:R})}let l=this.resources.get(u);l!==void 0&&this.routeMap.delete(l.routePath),this.resources.set(u,{operations:[...o],routePath:c,resource:d,records:a,settings:nr(s),sortableFields:[...i]}),this.routeMap.set(c,u)}resetResource(t,r,n,o=j,s={},i=[]){this.registerResource(t,r,n,o,s,i)}list(t){return[...this.getResourceState(t).records.values()]}resolveResourceName(t){return this.routeMap.get(Xe(t))}listResources(){return[...this.resources.values()]}get(t,r){return this.getResourceState(t).records.get(r)}getResponseKind(t){return this.getResourceState(t).resource.responseKind}getOperations(t){return[...this.getResourceState(t).operations]}getSettings(t){return{...this.getResourceState(t).settings}}getSortableFields(t){return[...this.getResourceState(t).sortableFields]}getSingle(t){return this.list(t)[0]}create(t,r){let n=this.getResourceState(t),o=typeof r.id=="string"&&r.id.length>0?r.id:crypto.randomUUID(),s={...r,id:o};return n.records.set(o,s),s}replace(t,r,n){let o=this.getResourceState(t);if(!o.records.has(r))return;let s={...n,id:r};return o.records.set(r,s),s}patch(t,r,n){let o=this.getResourceState(t),s=o.records.get(r);if(s===void 0)return;let i={...s,...n,id:r};return o.records.set(r,i),i}replaceSingle(t,r){let n=this.getResourceState(t),o=this.getSingle(t),s=typeof o?.id=="string"&&o.id.length>0?o.id:crypto.randomUUID(),i={...r,id:s};return n.records.clear(),n.records.set(s,i),i}patchSingle(t,r){let n=this.getSingle(t)??{};return this.replaceSingle(t,{...n,...r})}deleteSingle(t){let r=this.getResourceState(t),n=r.records.size>0;return r.records.clear(),n}delete(t,r){return this.getResourceState(t).records.delete(r)}getResourceState(t){let r=this.resources.get(t);if(r===void 0)throw new Error(`Resource "${t}" is not registered.`);return r}};function tr(e){return typeof e.id=="string"&&e.id.length>0?e.id:crypto.randomUUID()}function rr(e,t){let r=e??`/${t.toLowerCase()}s`;return Xe(r)}function Xe(e){return e.replace(/^\/+/,"").toLowerCase()}function nr(e){return{...e,pagination:e.pagination===void 0?void 0:{...e.pagination}}}var ee=class{constructor(t,r={}){this.db=t;this.seedOnRegister=r.seedOnRegister??!1}db;resources=new Map;routeMap=new Map;seedOnRegister;registerResource(t,r,n,o=j,s={},i=[]){let a=this.createState(t,n,o,s,i);this.ensureRecordTable(a.tableName),(this.seedOnRegister||this.isTableEmpty(a.tableName))&&this.replaceAllRecords(a.tableName,r)}resetResource(t,r,n,o=j,s={},i=[]){let a=this.createState(t,n,o,s,i);this.ensureRecordTable(a.tableName),this.replaceAllRecords(a.tableName,r)}list(t){let r=this.getResourceState(t);return this.db.prepare(`SELECT payload FROM ${M(r.tableName)} ORDER BY _created_at ASC, rowid ASC`).all().map(o=>qe(o.payload))}resolveResourceName(t){return this.routeMap.get(Ge(t))}listResources(){return[...this.resources.values()]}get(t,r){let n=this.getResourceState(t),o=this.db.prepare(`SELECT payload FROM ${M(n.tableName)} WHERE id = ?`).get(r);return o===void 0?void 0:qe(o.payload)}getResponseKind(t){return this.getResourceState(t).resource.responseKind}getOperations(t){return[...this.getResourceState(t).operations]}getSettings(t){return{...this.getResourceState(t).settings}}getSortableFields(t){return[...this.getResourceState(t).sortableFields]}getSingle(t){return this.list(t)[0]}create(t,r){let n=this.getResourceState(t),o=typeof r.id=="string"&&r.id.length>0?r.id:le.randomUUID(),s={...r,id:o};return this.db.prepare(`
|
|
40
|
+
INSERT INTO ${M(n.tableName)} (id, payload)
|
|
41
|
+
VALUES (?, ?)
|
|
42
|
+
`).run(o,pe(s)),s}replace(t,r,n){if(this.get(t,r)===void 0)return;let s=this.getResourceState(t),i={...n,id:r};return this.db.prepare(`
|
|
43
|
+
UPDATE ${M(s.tableName)}
|
|
44
|
+
SET payload = ?, _updated_at = datetime('now')
|
|
45
|
+
WHERE id = ?
|
|
46
|
+
`).run(pe(i),r),i}patch(t,r,n){let o=this.get(t,r);if(o!==void 0)return this.replace(t,r,{...o,...n,id:r})}replaceSingle(t,r){let n=this.getSingle(t),o=typeof n?.id=="string"&&n.id.length>0?n.id:le.randomUUID(),s={...r,id:o},i=this.getResourceState(t);return this.replaceAllRecords(i.tableName,[s]),s}patchSingle(t,r){let n=this.getSingle(t)??{};return this.replaceSingle(t,{...n,...r})}deleteSingle(t){let r=this.getResourceState(t),n=this.list(t).length;return this.db.prepare(`DELETE FROM ${M(r.tableName)}`).run(),n>0}delete(t,r){let n=this.getResourceState(t);return this.db.prepare(`DELETE FROM ${M(n.tableName)} WHERE id = ?`).run(r).changes>0}createState(t,r,n,o,s){let i=sr(r,t.resourceName),a=i,c=this.resources.get(a);c!==void 0&&this.routeMap.delete(c.routePath);let u={...t,responseKind:t.responseKind??"collection"},d={operations:[...n],records:new Map,resource:u,routePath:i,settings:ir(o),sortableFields:[...s],tableName:ar(i)};return this.resources.set(a,d),this.routeMap.set(i,a),d}ensureRecordTable(t){this.db.exec(`
|
|
47
|
+
CREATE TABLE IF NOT EXISTS ${M(t)} (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
payload TEXT NOT NULL,
|
|
50
|
+
_created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
51
|
+
_updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
52
|
+
)
|
|
53
|
+
`)}isTableEmpty(t){return this.db.prepare(`SELECT COUNT(*) AS count FROM ${M(t)}`).get().count===0}replaceAllRecords(t,r){let n=M(t),o=this.db.prepare(`DELETE FROM ${n}`),s=this.db.prepare(`
|
|
54
|
+
INSERT INTO ${n} (id, payload)
|
|
55
|
+
VALUES (?, ?)
|
|
56
|
+
`);this.db.transaction(a=>{o.run();for(let c of a){let u=or(c),d={...c,id:u};s.run(u,pe(d))}})(r)}getResourceState(t){let r=this.resources.get(t);if(r===void 0)throw new Error(`Resource "${t}" is not registered.`);return r}};function pe(e){return JSON.stringify(e)}function qe(e){let t=JSON.parse(e);return t!==null&&typeof t=="object"&&!Array.isArray(t)?t:{}}function or(e){return typeof e.id=="string"&&e.id.length>0?e.id:le.randomUUID()}function sr(e,t){let r=e??`/${t.toLowerCase()}s`;return Ge(r)}function Ge(e){return e.replace(/^\/+/,"").toLowerCase()}function ir(e){return{...e,pagination:e.pagination===void 0?void 0:{...e.pagination}}}function M(e){return`"${e.replaceAll('"','""')}"`}function ar(e){let t=e.toLowerCase().replace(/[^a-z0-9_]+/g,"_").replace(/^_+|_+$/g,""),r=t.length>0?t:"resource";return/^[a-z_]/.test(r)?r:`resource_${r}`}import{faker as p}from"@faker-js/faker";var Ye=new Map([["email","email"],["firstname","firstName"],["lastname","lastName"],["phone","phone"],["url","url"],["link","url"],["id","uuid"],["price","price"],["cost","price"],["amount","price"],["description","paragraph"],["bio","paragraph"],["summary","paragraph"],["city","city"],["country","country"],["address","address"]]);function cr(e,t){let r=e.toLowerCase();return t==="boolean"?"boolean":Ye.has(r)?Ye.get(r)??"unknown":r.endsWith("email")?"email":r==="name"||r.endsWith("name")?"name":r.endsWith("at")?"datetime":r.endsWith("date")?"date":r.endsWith("id")?"uuid":r.endsWith("url")?"url":r.endsWith("phone")?"phone":r.startsWith("is")||r.startsWith("has")?"boolean":"unknown"}function Je(e){return{...e,fields:e.fields.map(ge)}}function ge(e){return{...e,semanticHint:cr(e.name,e.type),arrayItemType:e.arrayItemType===void 0?void 0:ge(e.arrayItemType),nestedFields:e.nestedFields?.map(ge)}}var fe="2026-01-01T00:00:00.000Z";function $(e,t={}){let r=t.count??20,n=Je(e);return t.seed!==void 0&&p.seed(t.seed),Array.from({length:r},()=>ur(n))}function ur(e){let t=[];for(let r of e.fields)r.optional&&p.datatype.boolean()||t.push([r.name,me(r)]);return Object.fromEntries(t)}function me(e){let t=mr(e);if(t!==void 0)return t;switch(e.type){case"array":return dr(e);case"boolean":return p.datatype.boolean();case"date":return Ze();case"enum":return pr(e);case"number":return lr(e);case"object":return fr(e);case"string":return gr(e);default:return null}}function dr(e){let t=e.arrayItemType;if(t===void 0)return[];let r=p.number.int({min:1,max:3});return Array.from({length:r},()=>me(t))}function pr(e){let t=e.enumValues??[];return t.length===0?null:p.helpers.arrayElement(t)}function lr(e){switch(e.semanticHint){case"price":return p.number.float({min:1,max:1e3,fractionDigits:2});case"percentage":return p.number.int({min:0,max:100});default:return p.number.int({min:1,max:9999})}}function gr(e){switch(e.semanticHint){case"email":return p.internet.email().toLowerCase();case"name":return p.person.fullName();case"firstName":return p.person.firstName();case"lastName":return p.person.lastName();case"phone":return p.phone.number();case"address":return p.location.streetAddress();case"city":return p.location.city();case"country":return p.location.country();case"url":return p.internet.url();case"uuid":return p.string.uuid();case"date":return p.date.recent({refDate:fe}).toISOString().slice(0,10);case"datetime":return p.date.recent({refDate:fe}).toISOString();case"paragraph":return p.lorem.paragraph();case"sentence":return p.lorem.sentence();default:return p.lorem.words({min:1,max:3})}}function fr(e){let t=e.nestedFields??[],r=[];for(let n of t)n.optional&&p.datatype.boolean()||r.push([n.name,me(n)]);return Object.fromEntries(r)}function mr(e){switch(e.fakerPath){case"commerce.price":return p.commerce.price();case"commerce.productName":return p.commerce.productName();case"datatype.boolean":return p.datatype.boolean();case"date.birthdate":return p.date.birthdate().toISOString();case"date.recent":return Ze();case"internet.email":return p.internet.email().toLowerCase();case"internet.ipv4":return p.internet.ipv4();case"internet.url":return p.internet.url();case"internet.userName":return p.internet.username();case"location.city":return p.location.city();case"location.country":return p.location.country();case"location.streetAddress":return p.location.streetAddress();case"lorem.paragraph":return p.lorem.paragraph();case"lorem.sentence":return p.lorem.sentence();case"lorem.words":return p.lorem.words({min:1,max:3});case"number.float":return p.number.float({min:1,max:1e3,fractionDigits:2});case"number.int":return p.number.int({min:1,max:9999});case"person.firstName":return p.person.firstName();case"person.fullName":return p.person.fullName();case"person.lastName":return p.person.lastName();case"phone.number":return p.phone.number();case"string.uuid":return p.string.uuid();default:return}}function Ze(){return p.date.recent({refDate:fe}).toISOString()}import m from"typescript";var hr=new Map([[m.SyntaxKind.StringKeyword,"string"],[m.SyntaxKind.NumberKeyword,"number"],[m.SyntaxKind.BooleanKeyword,"boolean"]]),A="MockApiStudioResponse",y=class extends Error{constructor(t){super(t),this.name="ParserError"}};function Re(e,t){let r=e?.trim(),n=t.trim();return r?n?`${r}
|
|
57
|
+
|
|
58
|
+
${n}`:r:n}function V(e,t="inline.ts"){let r=m.createSourceFile(t,e,m.ScriptTarget.Latest,!0,m.ScriptKind.TS),n=yr(r);if(n.enums.size===0&&n.interfaces.size===0&&n.typeAliases.size===0)throw new y("Expected at least one interface, type, or enum declaration, but found none.");let o=n.typeAliases.get(A),s=n.interfaces.get(A);if(o!==void 0||s!==void 0)return Rr({registry:n,rootInterface:s,rootTypeAlias:o,sourceFile:r});if(n.interfaces.size===1&&n.typeAliases.size===0&&n.enums.size===0){let[i]=n.interfaces.values();return Se(i,r,n,"collection")}throw new y(`Declare a root response with \`type ${A} = User;\` for a single response or \`type ${A} = User[];\` for a collection.`)}function yr(e){let t={enums:new Map,interfaces:new Map,typeAliases:new Map};for(let r of e.statements){if(m.isInterfaceDeclaration(r)){he(t,r.name.text),t.interfaces.set(r.name.text,r);continue}if(m.isTypeAliasDeclaration(r)){he(t,r.name.text),t.typeAliases.set(r.name.text,r);continue}m.isEnumDeclaration(r)&&(he(t,r.name.text),t.enums.set(r.name.text,r))}return t}function he(e,t){if(e.interfaces.has(t)||e.typeAliases.has(t)||e.enums.has(t))throw new y(`Duplicate declaration "${t}" is not supported.`)}function Rr({registry:e,rootInterface:t,rootTypeAlias:r,sourceFile:n}){if(t!==void 0&&r!==void 0)throw new y(`Only one root declaration named "${A}" is allowed.`);if(t!==void 0)return Se(t,n,e,"single");if(r===void 0)throw new y(`Missing root declaration "${A}".`);return tt(r.type,A,n,e,[])}function Se(e,t,r,n){return{responseKind:n,resourceName:e.name.text,fields:e.members.map(o=>te(o,t,r,[]))}}function tt(e,t,r,n,o){if(m.isArrayTypeNode(e))return et(e.elementType,t,r,n,o);if(m.isTypeReferenceNode(e)){if(m.isIdentifier(e.typeName)&&e.typeName.text==="Array"&&e.typeArguments?.length===1)return et(e.typeArguments[0],t,r,n,o);let i=e.typeName.getText(r);return Sr(i,t,r,n,o)}let s=D(e,r,t,n,o);if(s.type!=="object"){let i=e.getText(r);throw new y(`Root response "${A}" must resolve to an object type or array of object types, received "${i}".`)}return{responseKind:"single",resourceName:t,fields:s.nestedFields??[]}}function et(e,t,r,n,o){let s=ye(e,r,t,n,o);if(s.type!=="object"){let i=e.getText(r);throw new y(`Root response "${A}" must resolve to an object type or array of object types, received "${i}[]".`)}return{responseKind:"collection",resourceName:s.name,fields:s.nestedFields??[]}}function Sr(e,t,r,n,o){let s=n.interfaces.get(e);if(s!==void 0)return Se(s,r,n,"single");let i=n.typeAliases.get(e);if(i!==void 0){let a=wr(o,e,t);return tt(i.type,e,r,n,a)}throw new y(`Unknown type reference "${e}" for property "${t}".`)}function te(e,t,r,n){if(!m.isPropertySignature(e)||e.type===void 0)throw new y("Only typed interface properties are supported.");if(!m.isIdentifier(e.name))throw new y("Only identifier property names are supported.");let o={name:e.name.text,...D(e.type,t,e.name.text,r,n),optional:e.questionToken!==void 0},s=br(e);return s!==void 0&&(o.fakerPath=s),o}function D(e,t,r,n,o){let s=hr.get(e.kind);if(s!==void 0)return{type:s};if(m.isParenthesizedTypeNode(e))return D(e.type,t,r,n,o);if(m.isUnionTypeNode(e))return{type:"enum",enumValues:e.types.map(c=>{if(!m.isLiteralTypeNode(c)||!m.isStringLiteral(c.literal)){let u=e.getText(t);throw new y(`Unsupported field type "${u}" for property "${r}".`)}return c.literal.text})};if(m.isTypeLiteralNode(e))return{type:"object",nestedFields:e.members.map(a=>te(a,t,n,o))};if(m.isArrayTypeNode(e))return{type:"array",arrayItemType:ye(e.elementType,t,r,n,o)};if(m.isTypeReferenceNode(e)){if(m.isIdentifier(e.typeName)&&e.typeName.text==="Array"&&e.typeArguments?.length===1)return{type:"array",arrayItemType:ye(e.typeArguments[0],t,r,n,o)};if(!m.isIdentifier(e.typeName))throw new y(`Unsupported field type "${e.getText(t)}" for property "${r}".`);let a=e.typeName.text;if(a==="Date")return{type:"date"};if(o.includes(a))throw new y(`Recursive type references are not supported for property "${r}".`);let c=n.enums.get(a);if(c!==void 0)return{type:"enum",enumValues:kr(c,r)};let u=n.interfaces.get(a);if(u!==void 0)return{type:"object",nestedFields:u.members.map(l=>te(l,t,n,[...o,a]))};let d=n.typeAliases.get(a);if(d!==void 0)return D(d.type,t,r,n,[...o,a]);throw new y(`Unknown type reference "${a}" for property "${r}".`)}let i=e.getText(t);throw new y(`Unsupported field type "${i}" for property "${r}".`)}function kr(e,t){return e.members.map(r=>{if(r.initializer===void 0||!m.isStringLiteral(r.initializer))throw new y(`Only string enum members are supported for property "${t}".`);return r.initializer.text})}function ye(e,t,r,n,o){if(m.isTypeReferenceNode(e)&&m.isIdentifier(e.typeName)){let s=e.typeName.text,i=n.interfaces.get(s);if(i!==void 0)return{name:s,type:"object",nestedFields:i.members.map(c=>te(c,t,n,[...o,s])),optional:!1};let a=n.typeAliases.get(s);if(a!==void 0)return{name:s,...D(a.type,t,r,n,[...o,s]),optional:!1}}return{name:`${r}Item`,...D(e,t,r,n,o),optional:!1}}function wr(e,t,r){if(e.includes(t))throw new y(`Recursive type references are not supported for property "${r}".`);return[...e,t]}function br(e){for(let t of m.getJSDocTags(e)){if(t.tagName.getText()!=="mock")continue;let r=typeof t.comment=="string"?t.comment.trim():"",n=/^faker\.([A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+)$/.exec(r);if(n!==null)return n[1]}}function rt(e,t,r,n){let o=V(Re(r,e));return{records:$(o,{count:(o.responseKind??"collection")==="collection"?n?.recordCount??t.recordCountPerResource:1,seed:t.seed??void 0}),resource:o}}function st(e,t=[]){let r=[];for(let n of t){let o=nt(e,n.parentEntity),s=nt(e,n.childEntity);if(o===void 0||s===void 0){r.push(`Skipping relationship ${n.parentEntity} -> ${n.childEntity}: resource not found.`);continue}let i=o.records.map(a=>a[n.parentKey]).filter(Pr);if(i.length===0){r.push(`Skipping relationship ${n.parentEntity} -> ${n.childEntity}: no parent key values found.`);continue}s.records=s.records.map((a,c)=>({...a,[n.childForeignKey]:i[c%i.length]}))}return r}function nt(e,t){let r=ot(t);return Object.entries(e).find(([o])=>ot(o)===r)?.[1]}function ot(e){return e.replace(/^\/+/,"").toLowerCase()}function Pr(e){return["boolean","number","string"].includes(typeof e)}import Ct from"express";import{Router as Er}from"express";function L(e){return`${e.toLowerCase()}s`}function it(e,t){let r=Er();return r.get("/resources",(n,o)=>{o.json({resources:t.listResources().map(s=>({fields:s.resource.fields,resourceName:s.resource.resourceName,route:`/${s.routePath}`}))})}),r.post("/reset",(n,o)=>{e.reset(),o.status(204).send()}),r.put("/config",(n,o)=>{let s=e.updateConfig(Ar(n.body));o.json(s)}),r.post("/generate",(n,o)=>{let s=n.body;if(typeof s?.interfaceSource!="string"||s.interfaceSource.length===0){o.status(400).json({error:"interfaceSource is required."});return}let i=e.generate(s.interfaceSource,s.options),a=t.listResources().find(c=>c.resource.resourceName===i.resourceName)?.routePath;o.status(201).json({resourceName:i.resourceName,route:`/${a??L(i.resourceName)}`})}),r}function Ar(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};let t=e,r={};return typeof t.errorRate=="number"&&(r.errorRate=t.errorRate),typeof t.latencyMs=="number"&&(r.latencyMs=t.latencyMs),typeof t.recordCountPerResource=="number"&&(r.recordCountPerResource=t.recordCountPerResource),typeof t.seed=="number"&&(r.seed=t.seed),r}var _={ascValue:"asc",descValue:"desc",directionParam:"order",pageParam:"page",pageSizeParam:"limit",paginationEnabled:!0,searchEnabled:!0,searchParam:"search",sortingEnabled:!0,sortByParam:"sortBy"};function at(e){let t=Object.fromEntries(Object.entries(e??{}).filter(([,r])=>r!==void 0));return{..._,...t}}function Pe(e,t=_,r,n=[],o){let s=Object.fromEntries(e.map(u=>[u.resource.resourceName,ht(u.resource.fields)])),i=r?.enabled===!0&&r.type==="bearer",a=Object.assign({},...e.map(u=>Cr(u.resource.resourceName,u.resource.responseKind??"collection",u.routePath,`#/components/schemas/${u.resource.resourceName}`,u.operations,t,i)),Mr(e,n,t,i)),c=Or(e.map(u=>Ae(u.resource.resourceName,u.routePath)));return Object.assign(a,xr()),i&&Object.assign(a,Ir(r)),{openapi:"3.1.0",info:{title:Tr(o),version:"1.0.0"},tags:[...i?[{name:"Auth"}]:[],...c.map(u=>({name:u})),{name:"Mock Control"}],paths:a,components:{schemas:s,...i?{securitySchemes:{BearerAuth:{bearerFormat:"JWT",scheme:"bearer",type:"http"}}}:{}}}}function Tr(e){let t=e?.trim();return t?`${t} (by Mock API Studio)`:"Mock API Studio"}function Ee(e="/openapi.json"){return`<!doctype html>
|
|
59
|
+
<html lang="en">
|
|
60
|
+
<head>
|
|
61
|
+
<meta charset="utf-8" />
|
|
62
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
63
|
+
<title>Mock API Studio Docs</title>
|
|
64
|
+
<link
|
|
65
|
+
rel="stylesheet"
|
|
66
|
+
href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"
|
|
67
|
+
/>
|
|
68
|
+
<style>
|
|
69
|
+
body { margin: 0; background: #faf7f2; }
|
|
70
|
+
#swagger-ui { max-width: 1200px; margin: 0 auto; }
|
|
71
|
+
.topbar { display: none; }
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<div id="swagger-ui"></div>
|
|
76
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
77
|
+
<script>
|
|
78
|
+
window.ui = SwaggerUIBundle({
|
|
79
|
+
dom_id: "#swagger-ui",
|
|
80
|
+
url: "${e}"
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>`}function Cr(e,t,r,n,o=j,s=_,i=!1){let a=r??`/${L(e)}`,c=Ae(e,r),u=new Set(o),d={},l={};return t==="single"?(u.has("get")&&(d.get=lt(`get${e}`,n,!1)),u.has("post")&&(d.post=be(`post${e}`,n)),u.has("put")&&(d.put=be(`put${e}`,n)),u.has("patch")&&(d.patch=be(`patch${e}`,n)),u.has("delete")&&(d.delete=ut(e,!1)),ke(d,c),we(d,i),Object.keys(d).length>0?{[a]:d}:{}):(u.has("get")&&(d.get=ft(e,n,s),l.get=lt(`get${e}`,n,!0)),u.has("post")&&(d.post=jr(e,n)),u.has("put")&&(l.put=gt(`replace${e}`,"200",n)),u.has("patch")&&(l.patch=gt(`patch${e}`,"200",n)),u.has("delete")&&(l.delete=ut(e,!0)),ke(d,c),ke(l,c),we(d,i),we(l,i),{...Object.keys(d).length>0?{[a]:d}:{},...Object.keys(l).length>0?{[`${a}/{id}`]:l}:{}})}function Mr(e,t,r,n){let o={};for(let s of t){let i=e.find(u=>ct(u.routePath??`/${L(u.resource.resourceName)}`)===ct(s.childEntity));if(i===void 0)continue;let a=i.resource.resourceName,c=ft(a,`#/components/schemas/${a}`,r);c.operationId=`list${a}ForParent`,c.parameters=[re(),...c.parameters??[]],c.tags=[Ae(a,i.routePath)],n&&(c.security=[{BearerAuth:[]}]),o[`${s.parentEntity}/{id}${s.childEntity}`]={get:c}}return o}function ke(e,t){for(let r of Object.values(e))r!==null&&typeof r=="object"&&!Array.isArray(r)&&(r.tags=[t])}function ct(e){return e.replace(/^\/+/,"").toLowerCase()}function we(e,t){if(t)for(let r of Object.values(e))r!==null&&typeof r=="object"&&!Array.isArray(r)&&(r.security=[{BearerAuth:[]}])}function Ae(e,t){return((t??`/${L(e)}`).split("/").map(s=>s.trim()).filter(s=>s.length>0&&!s.startsWith("{")&&!s.startsWith(":")).at(-1)??e).split(/[-_\s]+/).filter(Boolean).map(s=>`${s.charAt(0).toUpperCase()}${s.slice(1)}`).join(" ")}function Or(e){return[...new Set(e)]}function ft(e,t,r){let n=[...r.paginationEnabled?[H(r.pageParam,"Page number, starting at 1","integer"),H(r.pageSizeParam,"Maximum records to return","integer")]:[],...r.searchEnabled?[H(r.searchParam,"Search term for partial field matching","string")]:[],...r.sortingEnabled?[H(r.sortByParam,"Field to sort by","string"),H(r.directionParam,"Sort direction","string")]:[]];return{operationId:`list${e}`,parameters:n,responses:{200:{description:`List ${e} records`,content:{"application/json":{schema:{type:"object",properties:{data:{type:"array",items:{$ref:t}},meta:{type:"object",properties:{limit:{type:"integer"},page:{type:"integer"},total:{type:"integer"},totalPages:{type:"integer"}},required:["limit","page","total","totalPages"]}},required:["data","meta"]}}}}}}}function ut(e,t){return{operationId:`delete${e}`,...t?{parameters:[re()]}:{},responses:{204:{description:`Deleted ${e} record`},404:{description:"Record not found"}}}}function be(e,t){return{operationId:e,requestBody:{required:!1,content:{"application/json":{schema:{type:"object"}}}},responses:{200:{description:"Mock response",content:{"application/json":{schema:{$ref:t}}}}}}}function jr(e,t){return{operationId:`create${e}`,requestBody:{required:!0,content:{"application/json":{schema:{$ref:t}}}},responses:{201:{description:`Created ${e} record`,content:{"application/json":{schema:{$ref:t}}}}}}}function xr(){return{"/_mock/resources":{get:{operationId:"listMockResources",tags:["Mock Control"],responses:{200:{description:"List registered mock resources"}}}},"/_mock/config":{put:{operationId:"updateMockConfig",tags:["Mock Control"],responses:{200:{description:"Updated runtime mock config"}}}},"/_mock/reset":{post:{operationId:"resetMockData",tags:["Mock Control"],responses:{200:{description:"Regenerated mock data"}}}},"/_mock/generate":{post:{operationId:"generateMockResource",tags:["Mock Control"],responses:{201:{description:"Created a new mock resource"}}}}}}function Ir(e){return{[e.loginPath]:{post:{operationId:"mockLogin",tags:["Auth"],requestBody:dt(),responses:{200:{description:"Authenticated mock user",content:{"application/json":{schema:pt()}}},[String(e.errorCode)]:{description:e.errorMessage}}}},[e.registerPath]:{post:{operationId:"mockRegister",tags:["Auth"],requestBody:dt(),responses:{201:{description:"Registered mock user",content:{"application/json":{schema:e.registerReturns==="user_and_token"?pt():vr()}}},409:{description:"Email already registered"}}}},...e.logoutPath?{[e.logoutPath]:{post:{operationId:"mockLogout",tags:["Auth"],responses:{200:{description:"Logged out mock user"}}}}}:{}}}function dt(){return{required:!0,content:{"application/json":{schema:{type:"object",properties:{email:{type:"string",format:"email"},password:{type:"string"}},required:["email","password"]}}}}}function pt(){return{type:"object",properties:{token:{type:"string"},user:mt()},required:["token","user"]}}function vr(){return{type:"object",properties:{user:mt()},required:["user"]}}function mt(){return{type:"object",properties:{id:{type:"string"},email:{type:"string",format:"email"},createdAt:{type:"string",format:"date-time"}},required:["id","email","createdAt"],additionalProperties:!0}}function lt(e,t,r){return{operationId:e,...r?{parameters:[re()]}:{},responses:{200:{description:"Single record response",content:{"application/json":{schema:{$ref:t}}}},404:{description:"Record not found"}}}}function gt(e,t,r){return{operationId:e,parameters:[re()],requestBody:{required:!0,content:{"application/json":{schema:{$ref:r}}}},responses:{[t]:{description:"Mutation response",content:{"application/json":{schema:{$ref:r}}}},404:{description:"Record not found"}}}}function re(){return{in:"path",name:"id",required:!0,schema:{type:"string"}}}function H(e,t,r){return{description:t,in:"query",name:e,required:!1,schema:{type:r}}}function ht(e){let t=Object.fromEntries(e.map(n=>[n.name,yt(n)])),r=e.filter(n=>!n.optional).map(n=>n.name);return{type:"object",properties:t,required:r}}function yt(e){switch(e.type){case"array":return{type:"array",items:e.arrayItemType===void 0?{}:yt(e.arrayItemType)};case"boolean":return{type:"boolean"};case"enum":return{type:"string",enum:e.enumValues??[]};case"number":return{type:"number"};case"object":return ht(e.nestedFields??[]);case"date":return{type:"string",format:"date"};default:return Nr(e)}}function Nr(e){switch(e.semanticHint){case"date":return{type:"string",format:"date"};case"datetime":return{type:"string",format:"date-time"};case"email":return{type:"string",format:"email"};case"url":return{type:"string",format:"uri"};case"uuid":return{type:"string",format:"uuid"};default:return{type:"string"}}}import{Router as $r}from"express";function bt(e,t=_,r=[]){let n=$r({mergeParams:!0});return n.get("/",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"get",s))return;if(e.getResponseKind(i)==="single"){let c=e.getSingle(i);if(c===void 0){s.status(404).json({error:"Record not found."});return}s.json(c);return}let a=St(e.list(i),o,e.getSettings(i),t,e.getSortableFields(i));if("error"in a){s.status(400).json({error:a.error});return}if(a.meta===void 0){s.json({data:a.data});return}s.json({data:a.data,meta:a.meta})}),n.get("/:id/:childResourcePath",(o,s)=>{let i=C(o.params),a=r.find(S=>B(S.parentEntity)===B(i??"")&&B(S.childEntity)===B(o.params.childResourcePath));if(a===void 0){s.status(404).json({error:"Nested resource relationship not found."});return}let c=E(e,a.parentEntity,s),u=E(e,a.childEntity,s);if(c===void 0||u===void 0||!T(e,u,"get",s))return;let d=Fr(e,c,a.parentKey,o.params.id);if(d===void 0){s.status(404).json({error:"Parent record not found."});return}let l=d[a.parentKey],g=e.list(u).filter(S=>Oe(S[a.childForeignKey],l)),R=St(g,o,e.getSettings(u),t,e.getSortableFields(u));if("error"in R){s.status(400).json({error:R.error});return}if(R.meta===void 0){s.json({data:R.data});return}s.json({data:R.data,meta:R.meta})}),n.get("/:id",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"get",s)||!ne(e,i,s))return;let a=e.get(i,o.params.id);if(a===void 0){s.status(404).json({error:"Record not found."});return}s.json(a)}),n.post("/",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"post",s))return;if(e.getResponseKind(i)==="single"){let c=e.getSingle(i);if(c===void 0){s.status(404).json({error:"Record not found."});return}s.json(c);return}let a=e.create(i,W(o.body));s.status(201).json(a)}),n.put("/",(o,s)=>{let i=E(e,C(o.params),s);i!==void 0&&T(e,i,"put",s)&&Te(e,i,s)&&s.json(e.replaceSingle(i,W(o.body)))}),n.patch("/",(o,s)=>{let i=E(e,C(o.params),s);i!==void 0&&T(e,i,"patch",s)&&Te(e,i,s)&&s.json(e.patchSingle(i,W(o.body)))}),n.delete("/",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"delete",s)||!Te(e,i,s))return;let a=e.getSingle(i);if(a===void 0){s.status(404).json({error:"Record not found."});return}Rt(e,i,a,r,s)&&(e.deleteSingle(i),s.status(204).send())}),n.put("/:id",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"put",s)||!ne(e,i,s))return;let a=e.replace(i,o.params.id,W(o.body));if(a===void 0){s.status(404).json({error:"Record not found."});return}s.json(a)}),n.patch("/:id",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"patch",s)||!ne(e,i,s))return;let a=e.patch(i,o.params.id,W(o.body));if(a===void 0){s.status(404).json({error:"Record not found."});return}s.json(a)}),n.delete("/:id",(o,s)=>{let i=E(e,C(o.params),s);if(i===void 0||!T(e,i,"delete",s)||!ne(e,i,s))return;let a=e.get(i,o.params.id);if(a===void 0){s.status(404).json({error:"Record not found."});return}if(!Rt(e,i,a,r,s))return;if(!e.delete(i,o.params.id)){s.status(404).json({error:"Record not found."});return}s.status(204).send()}),n}function ne(e,t,r){return e.getResponseKind(t)==="collection"?!0:(r.status(404).json({error:"Resource does not expose item routes."}),!1)}function Te(e,t,r){return e.getResponseKind(t)==="single"?!0:(r.status(404).json({error:"Collection resources do not expose root mutation routes."}),!1)}function T(e,t,r,n){return e.getOperations(t).includes(r)?!0:(n.status(405).json({error:"Operation not enabled for this resource."}),!1)}function E(e,t,r){if(t===void 0){r.status(404).json({error:"Resource not found."});return}let n=e.resolveResourceName(t);if(n===void 0){r.status(404).json({error:"Resource not found."});return}return n}function Rt(e,t,r,n,o){let s=n.find(i=>{let a=e.resolveResourceName(i.parentEntity),c=e.resolveResourceName(i.childEntity);if(a!==t||c===void 0)return!1;let u=r[i.parentKey];return u==null?!1:e.list(c).some(d=>Oe(d[i.childForeignKey],u))});return s===void 0?!0:(o.status(409).json({error:`Cannot delete record because related records in ${Ur(s.childEntity)} still reference it.`}),!1)}function W(e){return e===null||typeof e!="object"||Array.isArray(e)?{}:e}function St(e,t,r,n,o){let s=[...e];if(n.searchEnabled){let P=Ce(t.query[n.searchParam])?.toLowerCase().trim();P!==void 0&&P.length>0&&(s=s.filter(F=>Dr(F,P)))}if(n.sortingEnabled){let P=Ce(t.query[n.sortByParam]),F=Ce(t.query[n.directionParam]);if(P!==void 0&&P.length>0){if(!o.includes(P))return{error:o.length>0?`Invalid sort field: "${P}". Allowed fields: ${o.join(", ")}.`:"Sorting is not configured for this endpoint. No sortable fields are defined."};s=Lr(s,P,F===n.descValue?"desc":"asc")}}let i=r.pagination;if(!n.paginationEnabled||i?.enabled===!1)return{data:s};let a=wt(i?.defaultLimit,25,1,i?.maxLimit??5e3),c=Math.max(1,i?.maxLimit??5e3),u=kt(t.query[n.pageSizeParam]),d=kt(t.query[n.pageParam]),l=wt(u,a,1,c),g=Math.max(1,d??1),R=(g-1)*l,S=s.length,x=S>0?Math.ceil(S/l):0;return{data:s.slice(R,R+l),meta:{hasNextPage:g<x,hasPrevPage:g>1,limit:l,page:g,total:S,totalPages:x,...n.pageParam!=="page"?{[n.pageParam]:g}:{},...n.pageSizeParam!=="limit"?{[n.pageSizeParam]:l}:{}}}}function Dr(e,t){return Object.values(e).some(r=>Me(r,t))}function Me(e,t){return e==null?!1:Array.isArray(e)?e.some(r=>Me(r,t)):typeof e=="object"?Object.values(e).some(r=>Me(r,t)):String(e).toLowerCase().includes(t)}function Lr(e,t,r){return[...e].sort((n,o)=>{let s=_r(n[t],o[t]);return r==="desc"?-s:s})}function _r(e,t){return e==null&&t==null?0:e==null?1:t==null?-1:typeof e=="string"&&typeof t=="string"?e.localeCompare(t):e<t?-1:e>t?1:0}function Fr(e,t,r,n){return r==="id"?e.get(t,n):e.list(t).find(o=>Oe(o[r],n))}function Oe(e,t){return e===t?!0:e==null||t===null||t===void 0?!1:String(e)===String(t)}function Ce(e){let t=Array.isArray(e)?e[0]:e;return typeof t=="string"?t:void 0}function kt(e){let t=Array.isArray(e)?e[0]:e;if(typeof t!="string"||t.trim().length===0)return;let r=Number(t);return Number.isInteger(r)?r:void 0}function wt(e,t,r,n){return Math.min(Math.max(e??t,r),n)}function C(e){if(!(e===null||typeof e!="object"))return e.resourcePath}function B(e){return e.replace(/^\/+/,"").toLowerCase()}function Ur(e){let t=B(e);return t.length>0?`/${t}`:"child resources"}var oe=class{constructor(t,r,n){this.store=t;this.config={...r};for(let o of n)this.resources.set(o.resource.resourceName,{count:o.records.length,operations:o.operations,routePath:o.routePath,resource:o.resource,settings:o.settings,sortableFields:o.sortableFields,source:o.source})}store;config;resources=new Map;getConfig(){return{...this.config}}updateConfig(t){return this.config={...this.config,...t},this.getConfig()}listResources(){return[...this.resources.values()].map(t=>t.resource)}reset(){for(let t of this.resources.values()){let r=$(t.resource,{count:this.getRecordCount(t),seed:this.config.seed});(this.store.resetResource?.bind(this.store)??this.store.registerResource.bind(this.store))(t.resource,r,t.routePath,t.operations,t.settings,t.sortableFields)}}generate(t,r){r!==void 0&&this.updateConfig(r);let n=V(t),o=$(n,{count:(n.responseKind??"collection")==="collection"?this.config.recordCountPerResource:1,seed:this.config.seed});return this.store.registerResource(n,o),this.resources.set(n.resourceName,{count:o.length,resource:n,source:t}),n}getRecordCount(t){return(t.resource.responseKind??"collection")==="single"?1:t.settings?.recordCount??t.count??this.config.recordCountPerResource}};function Pt(e,t){return(r,n,o)=>{let s=()=>{n.status(e.errorCode).json({error:e.errorMessage})};if(e.type==="bearer"){let i=r.headers.authorization;if(!i?.startsWith("Bearer ")||t===void 0){s();return}let a=t.validateToken(i.slice(7));if(a===null){s();return}r.mockUser=a,o();return}if(e.type==="api_key"){let i=Kr(r,e);if(i===void 0||e.apiKeyValue!==void 0&&e.apiKeyValue.length>0&&i!==e.apiKeyValue){s();return}o();return}if(e.type==="basic"){let i=r.headers.authorization;if(!i?.startsWith("Basic ")){n.setHeader("WWW-Authenticate",'Basic realm="mock"'),s();return}let a=Buffer.from(i.slice(6),"base64").toString("utf8"),c=a.indexOf(":"),u=c===-1?a:a.slice(0,c),d=c===-1?"":a.slice(c+1);if(e.basicUsername!==void 0&&e.basicUsername.length>0&&u!==e.basicUsername||e.basicPassword!==void 0&&e.basicPassword.length>0&&d!==e.basicPassword){s();return}o();return}s()}}function Kr(e,t){if(t.apiKeyLocation==="query"){let o=e.query[t.apiKeyParam??"api_key"];return Array.isArray(o)?String(o[0]):typeof o=="string"?o:void 0}let r=t.apiKeyHeader??"X-Api-Key",n=e.headers[r.toLowerCase()];return Array.isArray(n)?n[0]:n}import{Router as zr}from"express";function Tt(e,t){let r=zr();return r.post(e.registerPath,(n,o)=>{let s=Et(n.body),i=At(s.email),a=typeof s.password=="string"?s.password:"";if(i.length===0||a.length===0){o.status(400).json({error:"email and password are required."});return}if(t.emailExists(i)){o.status(409).json({error:"An account with this email already exists."});return}let{password:c,...u}=s,d=t.register({email:i,password:a,profile:u});if(e.registerReturns==="user_and_token"){o.status(201).json({token:t.issueToken(d.id,e.tokenExpiry),user:d});return}o.status(201).json({user:d})}),r.post(e.loginPath,(n,o)=>{let s=Et(n.body),i=At(s.email),a=typeof s.password=="string"?s.password:"";if(i.length===0||a.length===0){o.status(400).json({error:"email and password are required."});return}let c=e.validateCredentials?t.validateCredentials(i,a):t.findUserByEmail(i)??t.register({email:i,password:a,profile:{}});if(c===null){o.status(e.errorCode).json({error:e.errorMessage});return}o.json({token:t.issueToken(c.id,e.tokenExpiry),user:c})}),e.logoutPath!==void 0&&r.post(e.logoutPath,(n,o)=>{let s=n.headers.authorization;s?.startsWith("Bearer ")&&t.revokeToken(s.slice(7)),o.json({message:"Logged out successfully."})}),r}function Et(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)?e:{}}function At(e){return typeof e=="string"?e.trim().toLowerCase():""}function je(e,t={},r,n){let o=Ct(),s=r??new z,i=at(t.queryConfig),a=t.relationships??[],c=Pe(e,i,t.authConfig,a,t.projectName),u=new oe(s,{errorRate:t.errorRate??0,latencyMs:t.latencyMs??0,port:t.port,recordCountPerResource:t.recordCountPerResource??20,seed:t.seed},e);o.use(Ct.json()),o.get("/openapi.json",(l,g)=>{g.json(c)}),o.get("/docs",(l,g)=>{g.type("html").send(Ee("/openapi.json"))}),t.authConfig?.enabled===!0&&t.authConfig.type==="bearer"&&n!==void 0&&o.use(Tt(t.authConfig,n)),o.use(async(l,g,R)=>{let S=u.getConfig();if(S.latencyMs!==void 0&&S.latencyMs>0&&await Vr(S.latencyMs),S.errorRate!==void 0&&S.errorRate>0&&Math.random()<S.errorRate){g.status(503).json({error:"Injected mock failure."});return}R()});for(let l of e)s.registerResource(l.resource,l.records,l.routePath,l.operations,l.settings,l.sortableFields);o.use("/_mock",it(u,s));let d=t.authConfig?.enabled===!0?[Pt(t.authConfig,n)]:[];return o.use("/:resourcePath",...d,bt(s,i,a)),o}function Vr(e){return new Promise(t=>{setTimeout(t,e)})}async function Mt({authConfig:e,authRepository:t,mockStore:r,port:n,projectName:o,queryConfig:s,relationships:i,settings:a,store:c}){let u=je(Object.entries(r).map(([l,g])=>({records:g.records,operations:g.operations,resource:g.resource,routePath:l,settings:g.settings,sortableFields:g.sortableFields,source:g.source})),{errorRate:a.errorRate,authConfig:e,latencyMs:a.latencyMs,port:n,projectName:o,queryConfig:s,recordCountPerResource:a.recordCountPerResource,relationships:i,seed:a.seed??void 0},c,t),d=await Hr(u,n);return{port:Wr(d,n),server:d}}async function Hr(e,t){return await new Promise((r,n)=>{let o=e.listen(t,"127.0.0.1",()=>r(o));o.once("error",n)})}function Wr(e,t){let r=e.address();return r===null||typeof r=="string"?t:r.port}var b={error(e){process.stderr.write(`${e}
|
|
85
|
+
`)},info(e){process.stdout.write(`${e}
|
|
86
|
+
`)},warn(e){process.stdout.write(`${e}
|
|
87
|
+
`)}};import Br from"dotenv";Br.config({quiet:!0});function Ot(e){if(e!==void 0&&e.length>0)return e;let t=O();return t?.token!==void 0?t.token:process.env.MOCK_API_TOKEN??null}var Qr=["get","post","put","patch","delete"],Xr=[{label:"GET",operation:"get",suffix:""},{label:"GET",operation:"get",suffix:"/:id"},{label:"POST",operation:"post",suffix:""},{label:"PUT",operation:"put",suffix:"/:id"},{label:"PATCH",operation:"patch",suffix:"/:id"},{label:"DELETE",operation:"delete",suffix:"/:id"}],qr=[{label:"GET",operation:"get"},{label:"POST",operation:"post"},{label:"PUT",operation:"put"},{label:"PATCH",operation:"patch"},{label:"DELETE",operation:"delete"}];async function Q({slug:e,port:t,token:r,workspaceId:n},o={}){let s=o.exit??(h=>process.exit(h)),i=Ot(r);if(i===null)return b.error(`No API token found.
|
|
88
|
+
|
|
89
|
+
Set it in your .env file: MOCK_API_TOKEN=your_token_here
|
|
90
|
+
Or pass it as a flag: npx mock-api-studio-cli --token your_token_here ${e}`),s(1);b.info("Connecting to Mock API Studio...");let a;try{a=await Ie(i,e,o.fetchImpl,n)}catch(h){return b.error(h instanceof Error?h.message:String(h)),s(1)}let{project:c,interfaces:u,settings:d}=a,l=Ue(c.slug),g=Ke(c.slug);ze(g);let R=c.relationships??[],S=Ve(u,d,R),x=He(g),P=Be(g),F=x!==null&&x!==S;if(u.length===0)return g.close(),b.warn(`Project "${c.name}" has no mock resources yet. Add some in your dashboard.`),s(0);let I=!l||P;if(F&&!I){if(b.warn("Interface definitions or endpoint settings have changed since the last local database seed."),process.stdin.isTTY){let h=await se.confirm({initialValue:!1,message:"Reset and regenerate local mock data with the new schema and endpoint settings?"});if(se.isCancel(h))return g.close(),s(0);I=h}else b.warn("Non-interactive run detected. Regenerating local mock data automatically."),I=!0;I||b.warn("Continuing with existing SQLite data. Some records may not match the latest schema or endpoint settings.")}b.info(`Generating mock data for ${u.length} mock resource(s)...`);let U={};for(let h of u)try{let v=rt(h.source,d,c.sharedTypes,h.settings);U[h.path]={operations:h.operations,records:v.records,resource:v.resource,settings:h.settings,sortableFields:h.sortableFields??[],source:h.source}}catch(v){b.warn(`Could not parse resource schema at path "${h.path}": ${v instanceof Error?v.message:String(v)}. Skipping.`)}for(let h of st(U,R))b.warn(h);let xe=u.filter(h=>h.path in U),Vt=xe.map(h=>({operations:h.operations,path:h.path,responseKind:U[h.path]?.resource.responseKind??"collection"}));if(xe.length===0)return g.close(),b.warn(`Project "${c.name}" has no valid mock resources yet. Add some in your dashboard.`),s(0);let Ht=t??c.localMockPort??4e3,Wt=new ee(g,{seedOnRegister:I}),Bt=c.authConfig?.enabled===!0&&c.authConfig.type==="bearer"?new Z(g):void 0,X=await Mt({authConfig:c.authConfig,authRepository:Bt,mockStore:U,port:Ht,projectName:c.name,queryConfig:{ascValue:c.ascValue,descValue:c.descValue,directionParam:c.directionParam,pageParam:c.pageParam,pageSizeParam:c.pageSizeParam,paginationEnabled:c.paginationEnabled,searchEnabled:c.searchEnabled,searchParam:c.searchParam,sortingEnabled:c.sortingEnabled,sortByParam:c.sortByParam},relationships:R,settings:d,store:Wt});return X.server.once("close",()=>g.close()),(I||x===null)&&(We(g,S),Qe(g)),Gr({interfaces:Vt,port:X.port,project:c}),{project:c,server:X.server,url:`http://127.0.0.1:${X.port}`}}function Gr({interfaces:e,port:t,project:r}){let n=`http://localhost:${t}`,o={ascValue:r.ascValue??"asc",descValue:r.descValue??"desc",directionParam:r.directionParam??"order",pageParam:r.pageParam??"page",pageSizeParam:r.pageSizeParam??"limit",paginationEnabled:r.paginationEnabled??!0,searchEnabled:r.searchEnabled??!0,searchParam:r.searchParam??"search",sortingEnabled:r.sortingEnabled??!0,sortByParam:r.sortByParam??"sortBy"};process.stdout.write(`
|
|
91
|
+
`),process.stdout.write(` \u2713 Authenticated
|
|
92
|
+
`),process.stdout.write(` \u2713 Project: ${r.name} (${r.slug})
|
|
93
|
+
`),process.stdout.write(` \u2713 ${e.length} mock resource(s) loaded
|
|
94
|
+
`),(r.relationships??[]).length>0&&process.stdout.write(` \u2713 ${r.relationships?.length??0} relationship(s) loaded
|
|
95
|
+
`),process.stdout.write(`
|
|
96
|
+
`),process.stdout.write(` Mock server running at ${n}
|
|
97
|
+
`),process.stdout.write(`
|
|
98
|
+
`),process.stdout.write(` Query params:
|
|
99
|
+
`),process.stdout.write(o.paginationEnabled?` Pagination ?${o.pageParam}=1&${o.pageSizeParam}=20
|
|
100
|
+
`:` Pagination disabled - all records returned
|
|
101
|
+
`),process.stdout.write(o.searchEnabled?` Search ?${o.searchParam}=<term>
|
|
102
|
+
`:` Search disabled
|
|
103
|
+
`),process.stdout.write(o.sortingEnabled?` Sorting ?${o.sortByParam}=<field>&${o.directionParam}=${o.ascValue}|${o.descValue}
|
|
104
|
+
`:` Sorting disabled
|
|
105
|
+
`),process.stdout.write(`
|
|
106
|
+
`),r.authConfig?.enabled===!0&&r.authConfig.type==="bearer"&&(process.stdout.write(` Auth routes:
|
|
107
|
+
`),process.stdout.write(` POST ${n}${r.authConfig.loginPath}
|
|
108
|
+
`),process.stdout.write(` POST ${n}${r.authConfig.registerPath}
|
|
109
|
+
`),r.authConfig.logoutPath&&process.stdout.write(` POST ${n}${r.authConfig.logoutPath}
|
|
110
|
+
`),process.stdout.write(`
|
|
111
|
+
`));for(let{operations:s,path:i,responseKind:a}of e){let c=new Set(s??Qr),u=a==="collection"?Xr:qr.map(d=>({...d,suffix:""}));for(let d of u)c.has(d.operation)&&process.stdout.write(` ${d.label} ${n}${i}${d.suffix}
|
|
112
|
+
`);process.stdout.write(`
|
|
113
|
+
`)}process.stdout.write(` Press Ctrl+C to stop.
|
|
114
|
+
|
|
115
|
+
`)}async function It(){for(f.intro("mock-api-studio");;)try{let e=await Jr();await Yr(e);return}catch(e){if(nn(e)){J(),f.log.error("Token is invalid. Generate a new token in your dashboard and try again.");continue}rn(e)}}async function Yr(e){let t=await en(e.token),r=await tn(t);if(r===null)return;let n=r.isPersonal?void 0:r.id,o=await Zr(e.token,n),s=r.isPersonal?o.filter(c=>c.readOnly!==!0):o;if(s.length===0){f.log.warn(o.length>0?"No project is available to sync. Select your Free project in the dashboard or upgrade.":"No projects found. Create one in your dashboard first."),f.outro("Done.");return}let i=await f.select({message:"Select a project to start",options:s.map(c=>({hint:on(c),label:c.name,value:c.slug}))});if(f.isCancel(i)){f.cancel("Cancelled.");return}let a=s.find(c=>c.slug===i);await Q({port:a?.localMockPort,slug:String(i),token:e.token,workspaceId:n})}async function Jr(){let e=O();if(e?.token===void 0){f.log.info("No API token found. Generate one in your dashboard and paste it here.");let n=await f.password({message:"API token",validate:i=>{if(i===void 0||i.trim().length===0)return"Token cannot be empty."}});f.isCancel(n)&&(f.cancel("Cancelled."),process.exit(0));let o=n.trim(),s=await jt(o,"Validating token...");try{K({email:s,token:o}),f.log.success("Token saved. You will not need to enter it again on this machine.")}catch(i){f.log.error(`Authenticated, but could not save the token at ${N()}: ${xt(i)}`),process.exit(1)}return{email:s,token:o}}let t=e.token,r=await jt(t,"Authenticating...");if(e.email!==r)try{K({...e,email:r})}catch(n){f.log.warn(`Could not cache account email at ${N()}: ${xt(n)}`)}else f.log.success(`Authenticated as ${r}`);return{email:r,token:t}}async function jt(e,t){let r=f.spinner();r.start(t);try{let n=await q(e);return r.stop(`Authenticated as ${n}`),n}catch(n){throw r.stop("Authentication failed."),n}}async function Zr(e,t){let r=f.spinner();r.start("Fetching projects...");try{let n=await ve(e,fetch,t);return r.stop(`Found ${n.length} project(s)`),n}catch(n){throw r.stop("Failed to fetch projects."),n}}async function en(e){let t=f.spinner();t.start("Fetching workspaces...");try{let r=await Ne(e);return t.stop(`Found ${r.length} workspace(s)`),r}catch(r){throw t.stop("Failed to fetch workspaces."),r}}async function tn(e){let t=e.filter(o=>o.accessStatus==="active"),r=e.filter(o=>o.accessStatus==="suspended");for(let o of r)f.log.warn(`${o.name} is suspended and cannot be selected.`);if(t.length===0)return f.log.warn("No accessible workspaces found. Ask the Team owner to restore access."),f.outro("Done."),null;if(t.length===1)return t[0]??null;let n=await f.select({message:"Select a workspace",options:t.map(o=>({hint:o.isPersonal?"Personal workspace":`Team workspace | ${o.role}`,label:o.name,value:o.id}))});return f.isCancel(n)?(f.cancel("Cancelled."),null):t.find(o=>o.id===n)??null}function rn(e){let t=e instanceof Error?e.message:String(e);t==="SUBSCRIPTION_INACTIVE"?f.log.error("Your subscription is inactive. Visit your dashboard to renew."):f.log.error(`Could not reach the server: ${t}`),process.exit(1)}function nn(e){return e instanceof Error&&e.message==="TOKEN_INVALID"}function on(e){let t=e.interfaceCount??0;return[e.slug,`${t} resource${t===1?"":"s"}`,e.description??""].filter(Boolean).join(" | ")}function xt(e){return e instanceof Error?e.message:String(e)}import*as k from"@clack/prompts";async function vt(){k.intro("mock-api-studio");let e=O();if(e?.token===void 0){k.log.info("No saved token found. Nothing to clear."),k.outro("Done.");return}let t=await k.confirm({initialValue:!1,message:`Remove saved token${e.email!==void 0?` for ${e.email}`:""}?`});if(k.isCancel(t)||!t){k.cancel("Cancelled.");return}J(),k.log.success("Token removed. Run mock-api-studio to authenticate again."),k.outro("Logged out.")}import*as w from"@clack/prompts";async function Nt(){w.intro("mock-api-studio");let e=O();if(e?.token===void 0){w.log.warn("No saved token found. Run mock-api-studio to authenticate."),w.outro("");return}if(e.email!==void 0){w.log.info(`Authenticated as ${e.email}`),w.outro("");return}let t=w.spinner();t.start("Fetching account info...");try{let r=await q(e.token);K({...e,email:r}),t.stop(`Authenticated as ${r}`),w.outro("")}catch(r){t.stop("Failed.");let n=r instanceof Error?r.message:String(r);n==="TOKEN_INVALID"?w.log.error("Saved token is no longer valid. Run mock-api-studio logout to clear it."):n==="SUBSCRIPTION_INACTIVE"?w.log.error("Your subscription is inactive. Visit your dashboard to renew."):w.log.error(n),process.exit(1)}}var $t="mock-api-studio",Dt="0.1.2",Lt="Start a local mock API server synced to your Mock API Studio account.";var un=new Set(["logout","start","whoami"]);function dn(){let e=new cn;return e.name($t).description(Lt).version(Dt).showHelpAfterError(),e.command("start").description("Start the mock server for a project by its slug.").argument("<slug>","Project slug").option("-p, --port <number>","Port to run the mock server on",zt).option("--token <string>","API token (overrides MOCK_API_TOKEN env var)").option("-w, --workspace <id>","Workspace ID for a joined Team project").action(async(t,r)=>{await Q({port:r.port,slug:t,token:r.token,workspaceId:r.workspace})}),e.command("logout").description("Remove the locally saved API token.").action(async()=>{await vt()}),e.command("whoami").description("Show which account the saved token belongs to.").action(async()=>{await Nt()}),e.argument("[slug]","Project slug. Skips interactive selection if provided.").option("-p, --port <number>","Port to run the mock server on",zt).option("--token <string>","API token override (not saved)").option("-w, --workspace <id>","Workspace ID for a joined Team project").action(async(t,r)=>{if(t!==void 0){await Q({port:r.port,slug:t,token:r.token,workspaceId:r.workspace});return}await It()}),e}async function pn(e){let t=fn(e);await dn().parseAsync(t)}var ln=gn(import.meta.url,process.argv[1]);ln&&pn(process.argv).catch(e=>{let t=e instanceof Error?e.message:String(e);process.stderr.write(`${t}
|
|
116
|
+
`),process.exitCode=1});function gn(e,t){if(t===void 0)return!1;let r=_t(an(e));if(Ut(t)){let o=r.replace(/^\/(?=[A-Za-z]:[\\/])/,"");return Ut(o)&&Ft.normalize(o).toLowerCase()===Ft.normalize(t).toLowerCase()}let n=_t(t);return process.platform==="win32"?r.toLowerCase()===n.toLowerCase():r===n||Kt(r)===Kt(n)}function Ut(e){return/^[A-Za-z]:[\\/]/.test(e)}function Kt(e){try{return sn(e)}catch{return e}}function fn(e){let t=e.slice(2),r=t[0];return r!==void 0&&!r.startsWith("-")&&!un.has(r)?[...e.slice(0,2),"start",...t]:e}function zt(e){let t=Number.parseInt(e,10);if(Number.isNaN(t))throw new Error(`Expected an integer but received "${e}".`);return t}export{dn as createProgram,gn as isCliEntrypoint,pn as run};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mock-api-studio-cli",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Start a local mock API server synced to your Mock API Studio account.",
|
|
5
|
+
"author": "Mock API Studio",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"homepage": "https://mockapistudio.dev/docs",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"mock-api-studio": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mock-api",
|
|
33
|
+
"cli",
|
|
34
|
+
"typescript",
|
|
35
|
+
"faker",
|
|
36
|
+
"express",
|
|
37
|
+
"api-testing",
|
|
38
|
+
"frontend-development"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@clack/prompts": "^1.6.0",
|
|
42
|
+
"@faker-js/faker": "^10.6.0",
|
|
43
|
+
"bcryptjs": "^3.0.3",
|
|
44
|
+
"better-sqlite3": "^11.10.0",
|
|
45
|
+
"commander": "^14.0.0",
|
|
46
|
+
"dotenv": "^17.2.1",
|
|
47
|
+
"express": "^5.2.1",
|
|
48
|
+
"typescript": "^5.8.3",
|
|
49
|
+
"uuid": "^14.0.1"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/bcryptjs": "^2.4.6",
|
|
53
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
54
|
+
"@types/express": "^5.0.3",
|
|
55
|
+
"@types/node": "^24.0.4",
|
|
56
|
+
"@types/supertest": "^7.2.0",
|
|
57
|
+
"supertest": "^7.1.1",
|
|
58
|
+
"tsup": "^8.5.0",
|
|
59
|
+
"vitest": "^3.2.4"
|
|
60
|
+
}
|
|
61
|
+
}
|