@onlook/capsule 0.1.0
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/dist/cli.d.ts +1 -0
- package/dist/cli.js +8257 -0
- package/dist/index.d.ts +319 -0
- package/dist/index.js +8027 -0
- package/package.json +77 -0
- package/src/runtime/CapsuleShell.test.tsx +144 -0
- package/src/runtime/CapsuleShell.tsx +189 -0
- package/src/runtime/browser-stubs.test.ts +178 -0
- package/src/runtime/browser-stubs.ts +201 -0
- package/src/runtime/msw-setup.test.ts +42 -0
- package/src/runtime/msw-setup.ts +63 -0
- package/src/runtime/safe-proxy.ts +143 -0
- package/src/runtime/setNestedValue.test.ts +45 -0
- package/src/types.ts +268 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/** A react-router parent route that wraps child routes via <Outlet /> */
|
|
5
|
+
type LayoutRoute = {
|
|
6
|
+
/** Route path segment, e.g. "/sign-in" */
|
|
7
|
+
path: string;
|
|
8
|
+
/** Relative path to the layout component */
|
|
9
|
+
component: string;
|
|
10
|
+
/** Named export (if omitted, assumes default export) */
|
|
11
|
+
exportName?: string;
|
|
12
|
+
};
|
|
13
|
+
/** A component that wraps <Routes> in the router (e.g. LocationManager, AuthGuard) */
|
|
14
|
+
type AppWrapper = {
|
|
15
|
+
/** Component name, e.g. "LocationManager" */
|
|
16
|
+
componentName: string;
|
|
17
|
+
/** Relative path to component file */
|
|
18
|
+
component: string;
|
|
19
|
+
/** Named export (if omitted, assumes default export) */
|
|
20
|
+
exportName?: string;
|
|
21
|
+
};
|
|
22
|
+
type RouteEntry = {
|
|
23
|
+
/** URL path, e.g. "/dashboard/settings" */
|
|
24
|
+
path: string;
|
|
25
|
+
/** Relative path to the page component, e.g. "app/dashboard/settings/page.tsx" */
|
|
26
|
+
pageComponent: string;
|
|
27
|
+
/** Layout files from outermost to innermost (Next.js) */
|
|
28
|
+
layouts: string[];
|
|
29
|
+
/** Parent route components that wrap this route via Outlet (react-router) */
|
|
30
|
+
layoutRoutes?: LayoutRoute[];
|
|
31
|
+
/** Mock values for dynamic segments, e.g. { id: "mock-123" } */
|
|
32
|
+
params?: Record<string, string>;
|
|
33
|
+
/** Named export to use (if omitted, assumes default export) */
|
|
34
|
+
exportName?: string;
|
|
35
|
+
};
|
|
36
|
+
type RouteManifest = {
|
|
37
|
+
framework: 'nextjs-app' | 'vite-react-router';
|
|
38
|
+
routes: RouteEntry[];
|
|
39
|
+
/** Components that wrap <Routes> in the router file (react-router only) */
|
|
40
|
+
appWrappers?: AppWrapper[];
|
|
41
|
+
};
|
|
42
|
+
type ComponentBoundary = {
|
|
43
|
+
/** Import source strings, e.g. ["@/contexts/AuthContext", "@tanstack/react-query"] */
|
|
44
|
+
imports: string[];
|
|
45
|
+
/** Hook call names, e.g. ["useAuth", "useQuery", "useFeatureFlags"] */
|
|
46
|
+
hooks: string[];
|
|
47
|
+
/** Context names from useContext calls, e.g. ["AuthContext", "ThemeContext"] */
|
|
48
|
+
contexts: string[];
|
|
49
|
+
};
|
|
50
|
+
type PropInfo = {
|
|
51
|
+
/** Property name */
|
|
52
|
+
name: string;
|
|
53
|
+
/** TypeScript type text, e.g. "string", "User | null" */
|
|
54
|
+
type: string;
|
|
55
|
+
/** Whether the prop is optional (question token or `| undefined`) */
|
|
56
|
+
optional: boolean;
|
|
57
|
+
};
|
|
58
|
+
type StateInfo = {
|
|
59
|
+
/** Variable name, e.g. "count" */
|
|
60
|
+
name: string;
|
|
61
|
+
/** Inferred type from generic or initial value */
|
|
62
|
+
type: string;
|
|
63
|
+
/** Setter name, e.g. "setCount" */
|
|
64
|
+
setter: string;
|
|
65
|
+
};
|
|
66
|
+
type HookCallInfo = {
|
|
67
|
+
/** Hook name, e.g. "useAuth" */
|
|
68
|
+
name: string;
|
|
69
|
+
/** Import source module, e.g. "@/hooks/useAuth"; empty string if locally defined */
|
|
70
|
+
module: string;
|
|
71
|
+
};
|
|
72
|
+
type ComponentProfile = {
|
|
73
|
+
props: PropInfo[];
|
|
74
|
+
state: StateInfo[];
|
|
75
|
+
hookCalls: HookCallInfo[];
|
|
76
|
+
};
|
|
77
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
78
|
+
[key: string]: JsonValue;
|
|
79
|
+
};
|
|
80
|
+
type ProviderSpec = {
|
|
81
|
+
/** Identifier for this provider, e.g. "auth" */
|
|
82
|
+
id: string;
|
|
83
|
+
/** Import path, e.g. "@/contexts/AuthContext" */
|
|
84
|
+
import: string;
|
|
85
|
+
/** Context variable name, e.g. "AuthContext" */
|
|
86
|
+
contextName: string;
|
|
87
|
+
/** Mock value to provide via Context.Provider */
|
|
88
|
+
mockValue: Record<string, unknown>;
|
|
89
|
+
};
|
|
90
|
+
type HookStubSpec = {
|
|
91
|
+
/** Module path to intercept, e.g. "@/hooks/useFeatureFlags" */
|
|
92
|
+
module: string;
|
|
93
|
+
/** Named export to stub, e.g. "useFeatureFlags" */
|
|
94
|
+
export: string;
|
|
95
|
+
/** Value the stub returns */
|
|
96
|
+
returns: unknown;
|
|
97
|
+
/** If true, export as a value instead of a function */
|
|
98
|
+
isValue?: boolean;
|
|
99
|
+
};
|
|
100
|
+
type NetworkMockSpec = {
|
|
101
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
102
|
+
/** URL path pattern, e.g. "/api/settings" */
|
|
103
|
+
path: string;
|
|
104
|
+
/** HTTP status code, defaults to 200 */
|
|
105
|
+
status?: number;
|
|
106
|
+
/** Response body */
|
|
107
|
+
response: unknown;
|
|
108
|
+
};
|
|
109
|
+
type CapsuleManifest = {
|
|
110
|
+
providers: ProviderSpec[];
|
|
111
|
+
hookStubs: HookStubSpec[];
|
|
112
|
+
networkMocks: NetworkMockSpec[];
|
|
113
|
+
/** Global assignments, e.g. { "window.ENV": { API_URL: "..." } } */
|
|
114
|
+
globals: Record<string, JsonValue>;
|
|
115
|
+
};
|
|
116
|
+
type CapsuleResult = {
|
|
117
|
+
status: 'sealed';
|
|
118
|
+
html: string;
|
|
119
|
+
manifest: CapsuleManifest;
|
|
120
|
+
attempts: number;
|
|
121
|
+
} | {
|
|
122
|
+
status: 'breached';
|
|
123
|
+
error: string;
|
|
124
|
+
componentStack: string;
|
|
125
|
+
manifest: CapsuleManifest;
|
|
126
|
+
};
|
|
127
|
+
type CapsuleStatus = {
|
|
128
|
+
route: RouteEntry;
|
|
129
|
+
status: 'sealed' | 'breached' | 'pending';
|
|
130
|
+
attempts: number;
|
|
131
|
+
error?: string;
|
|
132
|
+
};
|
|
133
|
+
type CapsuleConfig = {
|
|
134
|
+
/** Absolute path to the project root */
|
|
135
|
+
projectRoot: string;
|
|
136
|
+
/** Override route discovery glob */
|
|
137
|
+
routes?: string;
|
|
138
|
+
/** Max seal loop retries per route (default: 3) */
|
|
139
|
+
maxRetries: number;
|
|
140
|
+
/** Parallel seal operations (default: 4) */
|
|
141
|
+
concurrency: number;
|
|
142
|
+
/** Start viewer after sealing */
|
|
143
|
+
serve: boolean;
|
|
144
|
+
/** Viewer port (default: 5123) */
|
|
145
|
+
port: number;
|
|
146
|
+
/** Cache directory (default: .capsules/cache) */
|
|
147
|
+
cacheDir: string;
|
|
148
|
+
/** Show seal loop details */
|
|
149
|
+
verbose: boolean;
|
|
150
|
+
/** Run LLM fixes immediately on failure instead of after all routes render (default: false) */
|
|
151
|
+
eager: boolean;
|
|
152
|
+
/** Max parallel LLM calls in lazy mode (default: 1) */
|
|
153
|
+
llmConcurrency: number;
|
|
154
|
+
/** Path to the router file for Vite projects (auto-detected if not set) */
|
|
155
|
+
routerFile?: string;
|
|
156
|
+
/** Additional esbuild externals beyond the defaults */
|
|
157
|
+
external?: string[];
|
|
158
|
+
/** Workspace modules to allow through (bypass auto-proxy). Matched as prefixes. */
|
|
159
|
+
allowedModules?: string[];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* No-op — oxc-parser is stateless (no shared Project to reset).
|
|
164
|
+
* Kept for API compatibility with consumers that call resetProject().
|
|
165
|
+
*/
|
|
166
|
+
declare function resetProject(): void;
|
|
167
|
+
/**
|
|
168
|
+
* Extract the external boundary of a component file:
|
|
169
|
+
* - All import source strings
|
|
170
|
+
* - All hook calls (functions starting with "use")
|
|
171
|
+
* - All context names from useContext() calls
|
|
172
|
+
*/
|
|
173
|
+
declare function extractBoundary(filePath: string): ComponentBoundary;
|
|
174
|
+
/**
|
|
175
|
+
* Compute a deterministic hash of a component boundary.
|
|
176
|
+
* This is the cache key — component internals can change freely
|
|
177
|
+
* without invalidating the manifest. Only boundary changes
|
|
178
|
+
* (new imports, hooks, contexts) trigger re-sealing.
|
|
179
|
+
*/
|
|
180
|
+
declare function boundaryHash(boundary: ComponentBoundary): string;
|
|
181
|
+
/**
|
|
182
|
+
* Extract a richer component profile for LLM context:
|
|
183
|
+
* - Props with types and optionality
|
|
184
|
+
* - State variables with inferred types
|
|
185
|
+
* - Hook calls with their import sources
|
|
186
|
+
*
|
|
187
|
+
* This does NOT affect the boundary hash / cache key.
|
|
188
|
+
*/
|
|
189
|
+
declare function extractProfile(filePath: string): ComponentProfile;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Look up a cached manifest by boundary hash.
|
|
193
|
+
* Returns null if no cache entry exists or the entry is invalid.
|
|
194
|
+
*/
|
|
195
|
+
declare function getCachedManifest(hash: string, outputDir: string): CapsuleManifest | null;
|
|
196
|
+
/**
|
|
197
|
+
* Store a manifest in the cache by boundary hash.
|
|
198
|
+
*/
|
|
199
|
+
declare function setCachedManifest(hash: string, manifest: CapsuleManifest, outputDir: string): void;
|
|
200
|
+
/**
|
|
201
|
+
* Clear the entire manifest cache.
|
|
202
|
+
*/
|
|
203
|
+
declare function clearCache(outputDir: string): void;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Detect the framework used in a project by checking for config files.
|
|
207
|
+
*/
|
|
208
|
+
declare function detectFramework(projectRoot: string): RouteManifest['framework'] | null;
|
|
209
|
+
/**
|
|
210
|
+
* Discover all routes in a project.
|
|
211
|
+
* Supports Next.js App Router and Vite + react-router.
|
|
212
|
+
*/
|
|
213
|
+
declare function discoverRoutes(projectRoot: string, routeGlob?: string, routerFile?: string): RouteManifest;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Generate a capsule shell file for a single route.
|
|
217
|
+
*
|
|
218
|
+
* The shell imports the page component and its layout chain,
|
|
219
|
+
* wraps them in CapsuleShell with the manifest, and exports
|
|
220
|
+
* a default component.
|
|
221
|
+
*/
|
|
222
|
+
declare function generateShell(route: RouteEntry, outputDir: string, projectRoot: string, framework?: RouteManifest['framework'], appWrappers?: AppWrapper[]): string;
|
|
223
|
+
/**
|
|
224
|
+
* Write capsule shells and empty manifests for all routes.
|
|
225
|
+
* Returns the list of generated shell file paths.
|
|
226
|
+
*/
|
|
227
|
+
declare function generateAllShells(routes: RouteEntry[], outputDir: string, projectRoot: string, framework?: RouteManifest['framework'], appWrappers?: AppWrapper[]): Map<string, string>;
|
|
228
|
+
|
|
229
|
+
type LLMProvider = {
|
|
230
|
+
generateManifestFix: (opts: {
|
|
231
|
+
error: string;
|
|
232
|
+
componentStack: string;
|
|
233
|
+
componentSource: string;
|
|
234
|
+
currentManifest: CapsuleManifest;
|
|
235
|
+
componentProfile?: ComponentProfile;
|
|
236
|
+
unstubbedHooks?: {
|
|
237
|
+
name: string;
|
|
238
|
+
module: string;
|
|
239
|
+
}[];
|
|
240
|
+
routeParams?: Record<string, string>;
|
|
241
|
+
}) => Promise<CapsuleManifest>;
|
|
242
|
+
};
|
|
243
|
+
type SealOptions = {
|
|
244
|
+
projectRoot: string;
|
|
245
|
+
outputDir: string;
|
|
246
|
+
maxRetries: number;
|
|
247
|
+
verbose: boolean;
|
|
248
|
+
llm?: LLMProvider;
|
|
249
|
+
/** Skip LLM calls — render once and return breached on failure */
|
|
250
|
+
skipLlm?: boolean;
|
|
251
|
+
/** Additional esbuild externals */
|
|
252
|
+
external?: string[];
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Run the seal loop for a single route.
|
|
256
|
+
*
|
|
257
|
+
* 1. Bundle the shell with esbuild
|
|
258
|
+
* 2. Render in happy-dom with MSW network mocks
|
|
259
|
+
* 3. On failure, check cache or call LLM for a manifest fix
|
|
260
|
+
* 4. Retry up to maxRetries times
|
|
261
|
+
*/
|
|
262
|
+
declare function sealRoute(route: RouteEntry, shellPath: string, manifestPath: string, options: SealOptions): Promise<CapsuleResult>;
|
|
263
|
+
/** Load the global manifest from the output directory */
|
|
264
|
+
declare function loadGlobalManifest(outputDir: string): CapsuleManifest;
|
|
265
|
+
/**
|
|
266
|
+
* Merge a global manifest with a per-route manifest.
|
|
267
|
+
* Per-route entries override global ones when keys match.
|
|
268
|
+
*/
|
|
269
|
+
declare function mergeManifests(global: CapsuleManifest, perRoute: CapsuleManifest): CapsuleManifest;
|
|
270
|
+
/**
|
|
271
|
+
* Create an LLM provider using the Vercel AI SDK + OpenRouter.
|
|
272
|
+
*/
|
|
273
|
+
declare function createOpenRouterLLM(apiKey: string, model?: string): Promise<LLMProvider>;
|
|
274
|
+
|
|
275
|
+
type SealResult = {
|
|
276
|
+
route: string;
|
|
277
|
+
result: CapsuleResult;
|
|
278
|
+
};
|
|
279
|
+
type SealFn = (route: RouteEntry, shellPath: string, manifestPath: string, options: SealOptions) => Promise<CapsuleResult>;
|
|
280
|
+
declare function runSealPool(routes: RouteEntry[], shells: Map<string, string>, outputDir: string, sealOpts: SealOptions, concurrency: number, seal?: SealFn): Promise<SealResult[]>;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Apply universal browser API stubs for happy-dom.
|
|
284
|
+
* Call once before importing any components.
|
|
285
|
+
*/
|
|
286
|
+
declare function applyBrowserStubs(): void;
|
|
287
|
+
|
|
288
|
+
type CapsuleShellProps = {
|
|
289
|
+
route: string;
|
|
290
|
+
manifest: CapsuleManifest;
|
|
291
|
+
children: React.ReactNode;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Generic wrapper that reads a CapsuleManifest and builds the render environment.
|
|
295
|
+
*
|
|
296
|
+
* 1. Applies global assignments from manifest.globals
|
|
297
|
+
* 2. Builds a provider tree from manifest.providers
|
|
298
|
+
* 3. Wraps children in an error boundary
|
|
299
|
+
*
|
|
300
|
+
* Note: MSW network mocks are set up externally by the seal loop,
|
|
301
|
+
* not inside this component (they need to be active before render).
|
|
302
|
+
* Hook stubs are also applied externally via module mocking.
|
|
303
|
+
*/
|
|
304
|
+
declare function CapsuleShell({ manifest, children }: CapsuleShellProps): react_jsx_runtime.JSX.Element;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Run a callback with MSW network mocks active.
|
|
308
|
+
* Uses a shared server to avoid patching fetch multiple times.
|
|
309
|
+
*/
|
|
310
|
+
declare function withMockServer<T>(mocks: NetworkMockSpec[], fn: () => T | Promise<T>): Promise<T>;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Start the capsule viewer dev server.
|
|
314
|
+
* Creates a Vite server that serves an iframe-based route browser.
|
|
315
|
+
* Each route shell renders in the browser via Vite's transform pipeline.
|
|
316
|
+
*/
|
|
317
|
+
declare function startViewer(outputDir: string, manifest: RouteManifest, port: number, projectRoot?: string, shells?: Map<string, string>): Promise<void>;
|
|
318
|
+
|
|
319
|
+
export { type AppWrapper, type CapsuleConfig, type CapsuleManifest, type CapsuleResult, CapsuleShell, type CapsuleStatus, type ComponentBoundary, type ComponentProfile, type HookCallInfo, type HookStubSpec, type LLMProvider, type LayoutRoute, type NetworkMockSpec, type PropInfo, type ProviderSpec, type RouteEntry, type RouteManifest, type SealFn, type SealOptions, type SealResult, type StateInfo, applyBrowserStubs, boundaryHash, clearCache, createOpenRouterLLM, detectFramework, discoverRoutes, extractBoundary, extractProfile, generateAllShells, generateShell, getCachedManifest, loadGlobalManifest, mergeManifests, resetProject, runSealPool, sealRoute, setCachedManifest, startViewer, withMockServer };
|