@mimicai/explorer 0.7.1
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/.turbo/turbo-build.log +24 -0
- package/CHANGELOG.md +10 -0
- package/LICENSE +190 -0
- package/dist/client/assets/index-6jl2Igpx.css +1 -0
- package/dist/client/assets/index-DrcjTHum.js +55 -0
- package/dist/client/index.html +14 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.js +210 -0
- package/index.html +13 -0
- package/package.json +45 -0
- package/postcss.config.js +6 -0
- package/server/index.ts +285 -0
- package/src/App.tsx +116 -0
- package/src/components/explorer/json-viewer.tsx +241 -0
- package/src/components/layout/header.tsx +57 -0
- package/src/components/layout/sidebar.tsx +132 -0
- package/src/index.css +69 -0
- package/src/lib/api.ts +57 -0
- package/src/lib/group-endpoints.ts +35 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/src/pages/adapter-view.tsx +128 -0
- package/src/pages/dashboard.tsx +134 -0
- package/src/pages/data-view.tsx +202 -0
- package/src/pages/endpoint-tester.tsx +239 -0
- package/tailwind.config.ts +49 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +11 -0
- package/vite.config.ts +22 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Mimic Explorer</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>M</text></svg>" />
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DrcjTHum.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-6jl2Igpx.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="min-h-screen bg-background text-foreground antialiased">
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// server/index.ts
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import Fastify from "fastify";
|
|
7
|
+
import fastifyStatic from "@fastify/static";
|
|
8
|
+
import fastifyCors from "@fastify/cors";
|
|
9
|
+
import { loadConfig } from "@mimicai/core";
|
|
10
|
+
async function startExplorer(options = {}) {
|
|
11
|
+
const port = options.port ?? 7879;
|
|
12
|
+
const cwd = options.cwd ?? process.cwd();
|
|
13
|
+
const config = await loadConfig(cwd);
|
|
14
|
+
const server = Fastify({ logger: false });
|
|
15
|
+
await server.register(fastifyCors, { origin: true });
|
|
16
|
+
server.get("/_api/config", async () => {
|
|
17
|
+
return {
|
|
18
|
+
domain: config.domain,
|
|
19
|
+
personas: config.personas,
|
|
20
|
+
llm: config.llm,
|
|
21
|
+
apis: config.apis ?? {},
|
|
22
|
+
databases: config.databases ?? {}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
server.get("/_api/adapters", async () => {
|
|
26
|
+
return await loadAdapterInfos(config, cwd);
|
|
27
|
+
});
|
|
28
|
+
server.get("/_api/data", async () => {
|
|
29
|
+
return await loadDataSummaries(config, cwd);
|
|
30
|
+
});
|
|
31
|
+
server.get("/_api/data/:persona", async (req) => {
|
|
32
|
+
const { persona } = req.params;
|
|
33
|
+
const dataPath = join(cwd, ".mimic", "data", `${persona}.json`);
|
|
34
|
+
try {
|
|
35
|
+
const raw = await readFile(dataPath, "utf-8");
|
|
36
|
+
return JSON.parse(raw);
|
|
37
|
+
} catch {
|
|
38
|
+
return { persona, tables: {}, apiResponses: {}, facts: [] };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const clientDir = join(__dirname, "..", "client");
|
|
43
|
+
await server.register(fastifyStatic, {
|
|
44
|
+
root: clientDir,
|
|
45
|
+
prefix: "/",
|
|
46
|
+
wildcard: false
|
|
47
|
+
});
|
|
48
|
+
server.setNotFoundHandler(async (_req, reply) => {
|
|
49
|
+
return reply.sendFile("index.html", clientDir);
|
|
50
|
+
});
|
|
51
|
+
await server.listen({ port, host: "0.0.0.0" });
|
|
52
|
+
return {
|
|
53
|
+
url: `http://localhost:${port}`,
|
|
54
|
+
stop: () => server.close()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function loadAdapterInfos(config, cwd) {
|
|
58
|
+
const apis = config.apis ?? {};
|
|
59
|
+
const infos = [];
|
|
60
|
+
let nextPort = 4101;
|
|
61
|
+
for (const [apiName, apiConfig] of Object.entries(apis)) {
|
|
62
|
+
const cfg = apiConfig;
|
|
63
|
+
const adapterId = cfg.adapter || apiName;
|
|
64
|
+
const pkg = `@mimicai/adapter-${adapterId}`;
|
|
65
|
+
const port = cfg.port ?? nextPort++;
|
|
66
|
+
const enabled = cfg.enabled !== false;
|
|
67
|
+
try {
|
|
68
|
+
const mod = await tryImport(pkg, cwd);
|
|
69
|
+
const manifest = findManifest(mod);
|
|
70
|
+
const AdapterClass = findAdapterClass(mod);
|
|
71
|
+
let endpoints = [];
|
|
72
|
+
let adapterBasePath = `/${adapterId}`;
|
|
73
|
+
if (AdapterClass) {
|
|
74
|
+
const adapter = new AdapterClass();
|
|
75
|
+
endpoints = adapter.getEndpoints();
|
|
76
|
+
adapterBasePath = adapter.basePath;
|
|
77
|
+
}
|
|
78
|
+
infos.push({
|
|
79
|
+
id: adapterId,
|
|
80
|
+
name: manifest?.name ?? adapterId,
|
|
81
|
+
description: manifest?.description ?? "",
|
|
82
|
+
type: manifest?.type ?? "api-mock",
|
|
83
|
+
basePath: adapterBasePath,
|
|
84
|
+
versions: manifest?.versions ?? [],
|
|
85
|
+
endpoints,
|
|
86
|
+
enabled,
|
|
87
|
+
port: enabled ? port : void 0
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
infos.push({
|
|
91
|
+
id: adapterId,
|
|
92
|
+
name: adapterId,
|
|
93
|
+
description: `Package ${pkg} not installed`,
|
|
94
|
+
type: "api-mock",
|
|
95
|
+
basePath: `/${adapterId}`,
|
|
96
|
+
versions: [],
|
|
97
|
+
endpoints: [],
|
|
98
|
+
enabled,
|
|
99
|
+
port: enabled ? port : void 0
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return infos;
|
|
104
|
+
}
|
|
105
|
+
async function loadDataSummaries(config, cwd) {
|
|
106
|
+
const summaries = [];
|
|
107
|
+
for (const persona of config.personas) {
|
|
108
|
+
const dataPath = join(cwd, ".mimic", "data", `${persona.name}.json`);
|
|
109
|
+
try {
|
|
110
|
+
const raw = await readFile(dataPath, "utf-8");
|
|
111
|
+
const data = JSON.parse(raw);
|
|
112
|
+
const tables = {};
|
|
113
|
+
if (data.tables) {
|
|
114
|
+
for (const [name, rows] of Object.entries(data.tables)) {
|
|
115
|
+
tables[name] = rows.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const apis = {};
|
|
119
|
+
if (data.apiResponses) {
|
|
120
|
+
for (const [adapterId, responseSet] of Object.entries(data.apiResponses)) {
|
|
121
|
+
const rs = responseSet;
|
|
122
|
+
const resources = {};
|
|
123
|
+
for (const [resource, arr] of Object.entries(rs.responses)) {
|
|
124
|
+
if (arr.length > 0) {
|
|
125
|
+
resources[resource] = arr.length;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (Object.keys(resources).length > 0) {
|
|
129
|
+
apis[adapterId] = resources;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
summaries.push({ persona: persona.name, tables, apis });
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return summaries;
|
|
138
|
+
}
|
|
139
|
+
async function tryImport(pkg, cwd) {
|
|
140
|
+
try {
|
|
141
|
+
return await import(
|
|
142
|
+
/* @vite-ignore */
|
|
143
|
+
pkg
|
|
144
|
+
);
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const req = createRequire(join(cwd, "package.json"));
|
|
149
|
+
let resolved = req.resolve(pkg);
|
|
150
|
+
if (resolved.endsWith(".cjs")) {
|
|
151
|
+
resolved = resolved.replace(/\.cjs$/, ".js");
|
|
152
|
+
}
|
|
153
|
+
return await import(
|
|
154
|
+
/* @vite-ignore */
|
|
155
|
+
resolved
|
|
156
|
+
);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const cliEntry = createRequire(join(cwd, "package.json")).resolve("@mimicai/cli");
|
|
161
|
+
const cliDir = dirname(cliEntry);
|
|
162
|
+
const req = createRequire(join(cliDir, "package.json"));
|
|
163
|
+
let resolved = req.resolve(pkg);
|
|
164
|
+
if (resolved.endsWith(".cjs")) {
|
|
165
|
+
resolved = resolved.replace(/\.cjs$/, ".js");
|
|
166
|
+
}
|
|
167
|
+
return await import(
|
|
168
|
+
/* @vite-ignore */
|
|
169
|
+
resolved
|
|
170
|
+
);
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
const startPath = process.argv[1] ?? cwd;
|
|
174
|
+
let dir = dirname(startPath);
|
|
175
|
+
for (let i = 0; i < 10; i++) {
|
|
176
|
+
try {
|
|
177
|
+
const req = createRequire(join(dir, "package.json"));
|
|
178
|
+
let resolved = req.resolve(pkg);
|
|
179
|
+
if (resolved.endsWith(".cjs")) {
|
|
180
|
+
resolved = resolved.replace(/\.cjs$/, ".js");
|
|
181
|
+
}
|
|
182
|
+
return await import(
|
|
183
|
+
/* @vite-ignore */
|
|
184
|
+
resolved
|
|
185
|
+
);
|
|
186
|
+
} catch {
|
|
187
|
+
const parent = dirname(dir);
|
|
188
|
+
if (parent === dir) break;
|
|
189
|
+
dir = parent;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`Cannot find ${pkg}`);
|
|
193
|
+
}
|
|
194
|
+
function findManifest(mod) {
|
|
195
|
+
return mod.manifest;
|
|
196
|
+
}
|
|
197
|
+
function findAdapterClass(mod) {
|
|
198
|
+
return Object.values(mod).find((v) => {
|
|
199
|
+
if (typeof v !== "function") return false;
|
|
200
|
+
try {
|
|
201
|
+
const instance = new v();
|
|
202
|
+
return instance.type === "api-mock";
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
export {
|
|
209
|
+
startExplorer
|
|
210
|
+
};
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Mimic Explorer</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>M</text></svg>" />
|
|
8
|
+
</head>
|
|
9
|
+
<body class="min-h-screen bg-background text-foreground antialiased">
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mimicai/explorer",
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "Interactive web UI for exploring Mimic mock data and endpoints",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/server/index.js",
|
|
9
|
+
"types": "./dist/server/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"fastify": "^5.2.1",
|
|
14
|
+
"@fastify/static": "^8.1.0",
|
|
15
|
+
"@fastify/cors": "^10.0.2",
|
|
16
|
+
"@mimicai/core": "0.9.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^19.0.0",
|
|
20
|
+
"@types/react-dom": "^19.0.0",
|
|
21
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
22
|
+
"autoprefixer": "^10.4.21",
|
|
23
|
+
"postcss": "^8.5.3",
|
|
24
|
+
"react": "^19.0.0",
|
|
25
|
+
"react-dom": "^19.0.0",
|
|
26
|
+
"tailwindcss": "^3.4.17",
|
|
27
|
+
"tsup": "^8.5.0",
|
|
28
|
+
"typescript": "^5.8.2",
|
|
29
|
+
"vite": "^6.2.0",
|
|
30
|
+
"clsx": "^2.1.1",
|
|
31
|
+
"tailwind-merge": "^3.0.2"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "^19.0.0",
|
|
35
|
+
"react-dom": "^19.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "vite",
|
|
39
|
+
"build": "vite build && tsup",
|
|
40
|
+
"build:ui": "vite build",
|
|
41
|
+
"build:server": "tsup",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"clean": "rm -rf dist"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/server/index.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import Fastify from 'fastify';
|
|
6
|
+
import fastifyStatic from '@fastify/static';
|
|
7
|
+
import fastifyCors from '@fastify/cors';
|
|
8
|
+
import { loadConfig, logger } from '@mimicai/core';
|
|
9
|
+
import type { MimicConfig, ApiMockAdapter, AdapterManifest } from '@mimicai/core';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface ExplorerOptions {
|
|
16
|
+
port?: number;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PersonaDataSummary {
|
|
21
|
+
persona: string;
|
|
22
|
+
tables: Record<string, number>;
|
|
23
|
+
apis: Record<string, Record<string, number>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface AdapterInfo {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
type: string;
|
|
31
|
+
basePath: string;
|
|
32
|
+
versions: string[];
|
|
33
|
+
endpoints: Array<{ method: string; path: string; description: string }>;
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
port?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Explorer Server
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export async function startExplorer(options: ExplorerOptions = {}): Promise<{
|
|
43
|
+
url: string;
|
|
44
|
+
stop: () => Promise<void>;
|
|
45
|
+
}> {
|
|
46
|
+
const port = options.port ?? 7879;
|
|
47
|
+
const cwd = options.cwd ?? process.cwd();
|
|
48
|
+
|
|
49
|
+
const config = await loadConfig(cwd);
|
|
50
|
+
const server = Fastify({ logger: false });
|
|
51
|
+
|
|
52
|
+
await server.register(fastifyCors, { origin: true });
|
|
53
|
+
|
|
54
|
+
// ── API routes ──────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
// GET /_api/config
|
|
57
|
+
server.get('/_api/config', async () => {
|
|
58
|
+
return {
|
|
59
|
+
domain: config.domain,
|
|
60
|
+
personas: config.personas,
|
|
61
|
+
llm: config.llm,
|
|
62
|
+
apis: config.apis ?? {},
|
|
63
|
+
databases: config.databases ?? {},
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// GET /_api/adapters
|
|
68
|
+
server.get('/_api/adapters', async () => {
|
|
69
|
+
return await loadAdapterInfos(config, cwd);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// GET /_api/data
|
|
73
|
+
server.get('/_api/data', async () => {
|
|
74
|
+
return await loadDataSummaries(config, cwd);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// GET /_api/data/:persona
|
|
78
|
+
server.get<{ Params: { persona: string } }>('/_api/data/:persona', async (req) => {
|
|
79
|
+
const { persona } = req.params;
|
|
80
|
+
const dataPath = join(cwd, '.mimic', 'data', `${persona}.json`);
|
|
81
|
+
try {
|
|
82
|
+
const raw = await readFile(dataPath, 'utf-8');
|
|
83
|
+
return JSON.parse(raw);
|
|
84
|
+
} catch {
|
|
85
|
+
return { persona, tables: {}, apiResponses: {}, facts: [] };
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ── Serve static React build ────────────────────────────────────────────
|
|
90
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
91
|
+
const clientDir = join(__dirname, '..', 'client');
|
|
92
|
+
|
|
93
|
+
await server.register(fastifyStatic, {
|
|
94
|
+
root: clientDir,
|
|
95
|
+
prefix: '/',
|
|
96
|
+
wildcard: false,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// SPA fallback — serve index.html for non-API routes
|
|
100
|
+
server.setNotFoundHandler(async (_req, reply) => {
|
|
101
|
+
return reply.sendFile('index.html', clientDir);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ── Start ────────────────────────────────────────────────────────────────
|
|
105
|
+
await server.listen({ port, host: '0.0.0.0' });
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
url: `http://localhost:${port}`,
|
|
109
|
+
stop: () => server.close(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Helpers
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
async function loadAdapterInfos(config: MimicConfig, cwd: string): Promise<AdapterInfo[]> {
|
|
118
|
+
const apis = config.apis ?? {};
|
|
119
|
+
const infos: AdapterInfo[] = [];
|
|
120
|
+
|
|
121
|
+
// Calculate ports matching mimic host's assignment logic
|
|
122
|
+
let nextPort = 4101;
|
|
123
|
+
|
|
124
|
+
for (const [apiName, apiConfig] of Object.entries(apis)) {
|
|
125
|
+
const cfg = apiConfig as Record<string, unknown>;
|
|
126
|
+
const adapterId = (cfg.adapter as string) || apiName;
|
|
127
|
+
const pkg = `@mimicai/adapter-${adapterId}`;
|
|
128
|
+
const port = (cfg.port as number) ?? nextPort++;
|
|
129
|
+
const enabled = cfg.enabled !== false;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const mod = await tryImport(pkg, cwd);
|
|
133
|
+
const manifest = findManifest(mod);
|
|
134
|
+
const AdapterClass = findAdapterClass(mod);
|
|
135
|
+
|
|
136
|
+
let endpoints: Array<{ method: string; path: string; description: string }> = [];
|
|
137
|
+
let adapterBasePath = `/${adapterId}`;
|
|
138
|
+
if (AdapterClass) {
|
|
139
|
+
const adapter = new AdapterClass();
|
|
140
|
+
endpoints = adapter.getEndpoints();
|
|
141
|
+
adapterBasePath = adapter.basePath;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
infos.push({
|
|
145
|
+
id: adapterId,
|
|
146
|
+
name: manifest?.name ?? adapterId,
|
|
147
|
+
description: manifest?.description ?? '',
|
|
148
|
+
type: manifest?.type ?? 'api-mock',
|
|
149
|
+
basePath: adapterBasePath,
|
|
150
|
+
versions: manifest?.versions ?? [],
|
|
151
|
+
endpoints,
|
|
152
|
+
enabled,
|
|
153
|
+
port: enabled ? port : undefined,
|
|
154
|
+
});
|
|
155
|
+
} catch {
|
|
156
|
+
infos.push({
|
|
157
|
+
id: adapterId,
|
|
158
|
+
name: adapterId,
|
|
159
|
+
description: `Package ${pkg} not installed`,
|
|
160
|
+
type: 'api-mock',
|
|
161
|
+
basePath: `/${adapterId}`,
|
|
162
|
+
versions: [],
|
|
163
|
+
endpoints: [],
|
|
164
|
+
enabled,
|
|
165
|
+
port: enabled ? port : undefined,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return infos;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function loadDataSummaries(config: MimicConfig, cwd: string): Promise<PersonaDataSummary[]> {
|
|
174
|
+
const summaries: PersonaDataSummary[] = [];
|
|
175
|
+
|
|
176
|
+
for (const persona of config.personas) {
|
|
177
|
+
const dataPath = join(cwd, '.mimic', 'data', `${persona.name}.json`);
|
|
178
|
+
try {
|
|
179
|
+
const raw = await readFile(dataPath, 'utf-8');
|
|
180
|
+
const data = JSON.parse(raw);
|
|
181
|
+
|
|
182
|
+
const tables: Record<string, number> = {};
|
|
183
|
+
if (data.tables) {
|
|
184
|
+
for (const [name, rows] of Object.entries(data.tables)) {
|
|
185
|
+
tables[name] = (rows as unknown[]).length;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const apis: Record<string, Record<string, number>> = {};
|
|
190
|
+
if (data.apiResponses) {
|
|
191
|
+
for (const [adapterId, responseSet] of Object.entries(data.apiResponses)) {
|
|
192
|
+
const rs = responseSet as { responses: Record<string, unknown[]> };
|
|
193
|
+
const resources: Record<string, number> = {};
|
|
194
|
+
for (const [resource, arr] of Object.entries(rs.responses)) {
|
|
195
|
+
if ((arr as unknown[]).length > 0) {
|
|
196
|
+
resources[resource] = (arr as unknown[]).length;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (Object.keys(resources).length > 0) {
|
|
200
|
+
apis[adapterId] = resources;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
summaries.push({ persona: persona.name, tables, apis });
|
|
206
|
+
} catch {
|
|
207
|
+
// No data file yet
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return summaries;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function tryImport(pkg: string, cwd: string): Promise<Record<string, unknown>> {
|
|
215
|
+
// 1. Bare specifier — works when resolvable from calling context
|
|
216
|
+
try {
|
|
217
|
+
return await import(/* @vite-ignore */ pkg);
|
|
218
|
+
} catch {
|
|
219
|
+
// fall through
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 2. Resolve from the project's node_modules
|
|
223
|
+
try {
|
|
224
|
+
const req = createRequire(join(cwd, 'package.json'));
|
|
225
|
+
let resolved = req.resolve(pkg);
|
|
226
|
+
if (resolved.endsWith('.cjs')) {
|
|
227
|
+
resolved = resolved.replace(/\.cjs$/, '.js');
|
|
228
|
+
}
|
|
229
|
+
return await import(/* @vite-ignore */ resolved);
|
|
230
|
+
} catch {
|
|
231
|
+
// fall through
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 3. Resolve from @mimicai/cli's location — adapters are dependencies of
|
|
235
|
+
// the CLI package, so they're resolvable from the CLI's node_modules.
|
|
236
|
+
// This works in both real user projects (npm install @mimicai/cli) and
|
|
237
|
+
// monorepos (workspace symlinks).
|
|
238
|
+
try {
|
|
239
|
+
const cliEntry = createRequire(join(cwd, 'package.json')).resolve('@mimicai/cli');
|
|
240
|
+
const cliDir = dirname(cliEntry);
|
|
241
|
+
const req = createRequire(join(cliDir, 'package.json'));
|
|
242
|
+
let resolved = req.resolve(pkg);
|
|
243
|
+
if (resolved.endsWith('.cjs')) {
|
|
244
|
+
resolved = resolved.replace(/\.cjs$/, '.js');
|
|
245
|
+
}
|
|
246
|
+
return await import(/* @vite-ignore */ resolved);
|
|
247
|
+
} catch {
|
|
248
|
+
// fall through
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 4. Walk up from the CLI binary (process.argv[1]) — covers pnpm
|
|
252
|
+
// workspaces where packages are hoisted to a .pnpm store.
|
|
253
|
+
const startPath = process.argv[1] ?? cwd;
|
|
254
|
+
let dir = dirname(startPath);
|
|
255
|
+
for (let i = 0; i < 10; i++) {
|
|
256
|
+
try {
|
|
257
|
+
const req = createRequire(join(dir, 'package.json'));
|
|
258
|
+
let resolved = req.resolve(pkg);
|
|
259
|
+
if (resolved.endsWith('.cjs')) {
|
|
260
|
+
resolved = resolved.replace(/\.cjs$/, '.js');
|
|
261
|
+
}
|
|
262
|
+
return await import(/* @vite-ignore */ resolved);
|
|
263
|
+
} catch {
|
|
264
|
+
const parent = dirname(dir);
|
|
265
|
+
if (parent === dir) break;
|
|
266
|
+
dir = parent;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw new Error(`Cannot find ${pkg}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function findManifest(mod: Record<string, unknown>): AdapterManifest | undefined {
|
|
274
|
+
return mod.manifest as AdapterManifest | undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function findAdapterClass(mod: Record<string, unknown>): (new () => ApiMockAdapter) | undefined {
|
|
278
|
+
return Object.values(mod).find((v) => {
|
|
279
|
+
if (typeof v !== 'function') return false;
|
|
280
|
+
try {
|
|
281
|
+
const instance = new (v as new () => unknown)() as { type?: string };
|
|
282
|
+
return instance.type === 'api-mock';
|
|
283
|
+
} catch { return false; }
|
|
284
|
+
}) as (new () => ApiMockAdapter) | undefined;
|
|
285
|
+
}
|