code-to-design 0.2.0 → 0.2.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.
|
@@ -335,6 +335,84 @@ async function detectAuth(projectRoot, allSources) {
|
|
|
335
335
|
return config;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
// ../core/src/analysis/endpoint-extractor.ts
|
|
339
|
+
function extractBaseUrl(source) {
|
|
340
|
+
const envFallback = source.match(
|
|
341
|
+
/process\.env\.\w+\s*\|\|\s*['"]([^'"]+)['"]/
|
|
342
|
+
);
|
|
343
|
+
if (envFallback) return envFallback[1];
|
|
344
|
+
const baseUrlProp = source.match(/baseURL\s*:\s*['"]([^'"]+)['"]/);
|
|
345
|
+
if (baseUrlProp) return baseUrlProp[1];
|
|
346
|
+
const constAssign = source.match(
|
|
347
|
+
/(?:API_BASE_URL|API_URL|BASE_URL)\s*=\s*['"]([^'"]+)['"]/
|
|
348
|
+
);
|
|
349
|
+
if (constAssign) return constAssign[1];
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
function extractEndpoints(apiClientSource) {
|
|
353
|
+
const endpoints = [];
|
|
354
|
+
const seen = /* @__PURE__ */ new Set();
|
|
355
|
+
const lines = apiClientSource.split("\n");
|
|
356
|
+
for (let i = 0; i < lines.length; i++) {
|
|
357
|
+
const line = lines[i];
|
|
358
|
+
const methodCallRegex = /\.\s*(get|post|put|delete|patch)\s*\(\s*(?:['"`])([^'"`$]*)/gi;
|
|
359
|
+
let match;
|
|
360
|
+
while ((match = methodCallRegex.exec(line)) !== null) {
|
|
361
|
+
const method = match[1].toUpperCase();
|
|
362
|
+
let path = match[2];
|
|
363
|
+
path = path.replace(/['"`)]+$/, "").trim();
|
|
364
|
+
if (!path || path.length === 0) continue;
|
|
365
|
+
const key = `${method} ${path}`;
|
|
366
|
+
if (seen.has(key)) continue;
|
|
367
|
+
seen.add(key);
|
|
368
|
+
const endpoint = { method, path };
|
|
369
|
+
const fnName = findFunctionName(lines, i);
|
|
370
|
+
if (fnName) endpoint.functionName = fnName;
|
|
371
|
+
endpoints.push(endpoint);
|
|
372
|
+
}
|
|
373
|
+
const fetchRegex = /\bfetch\s*\(\s*['"`]([^'"`$]+)/g;
|
|
374
|
+
while ((match = fetchRegex.exec(line)) !== null) {
|
|
375
|
+
const path = match[1].replace(/['"`)]+$/, "").trim();
|
|
376
|
+
if (!path || path.length === 0) continue;
|
|
377
|
+
const key = `GET ${path}`;
|
|
378
|
+
if (seen.has(key)) continue;
|
|
379
|
+
seen.add(key);
|
|
380
|
+
const endpoint = { method: "GET", path };
|
|
381
|
+
const fnName = findFunctionName(lines, i);
|
|
382
|
+
if (fnName) endpoint.functionName = fnName;
|
|
383
|
+
endpoints.push(endpoint);
|
|
384
|
+
}
|
|
385
|
+
const templateRegex = /\.\s*(get|post|put|delete|patch)\s*\(\s*`([^`]*?)\$\{/gi;
|
|
386
|
+
while ((match = templateRegex.exec(line)) !== null) {
|
|
387
|
+
const method = match[1].toUpperCase();
|
|
388
|
+
const path = match[2].trim();
|
|
389
|
+
if (!path || path.length === 0) continue;
|
|
390
|
+
const key = `${method} ${path}`;
|
|
391
|
+
if (seen.has(key)) continue;
|
|
392
|
+
seen.add(key);
|
|
393
|
+
const endpoint = { method, path };
|
|
394
|
+
const fnName = findFunctionName(lines, i);
|
|
395
|
+
if (fnName) endpoint.functionName = fnName;
|
|
396
|
+
endpoints.push(endpoint);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return endpoints;
|
|
400
|
+
}
|
|
401
|
+
function findFunctionName(lines, lineIndex) {
|
|
402
|
+
for (let i = lineIndex; i >= Math.max(0, lineIndex - 3); i--) {
|
|
403
|
+
const line = lines[i];
|
|
404
|
+
const constArrow = line.match(/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:\([^)]*\)\s*=>|\([^)]*\)\s*:\s*\w[^=]*=>)/);
|
|
405
|
+
if (constArrow) return constArrow[1];
|
|
406
|
+
const propMatch = line.match(/(\w+)\s*:\s*(?:\([^)]*\)\s*=>|function\b)/);
|
|
407
|
+
if (propMatch) return propMatch[1];
|
|
408
|
+
const fnMatch = line.match(/(?:async\s+)?function\s+(\w+)/);
|
|
409
|
+
if (fnMatch) return fnMatch[1];
|
|
410
|
+
const methodMatch = line.match(/^\s*(\w+)\s*\([^)]*\)\s*\{/);
|
|
411
|
+
if (methodMatch) return methodMatch[1];
|
|
412
|
+
}
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
|
|
338
416
|
// ../core/src/analysis/code-analyzer.ts
|
|
339
417
|
var DEFAULT_MAX_TOKENS = 8e3;
|
|
340
418
|
var CHARS_PER_TOKEN = 4;
|
|
@@ -454,6 +532,58 @@ function estimateTokens(text) {
|
|
|
454
532
|
function hasApiCalls(source) {
|
|
455
533
|
return /\bfetch\s*\(/.test(source) || /\baxios\b/.test(source) || /\buseQuery\b/.test(source) || /\buseMutation\b/.test(source) || /\bapiRequest\b/.test(source) || /\bgetServerSideProps\b/.test(source) || /\bgetStaticProps\b/.test(source) || /API_BASE_URL/.test(source) || /['"]\/api\//.test(source) || /from\s+['"].*api[-_]?client.*['"]/.test(source) || /from\s+['"].*\/api['"]/.test(source) || /from\s+['"].*\/services\//.test(source);
|
|
456
534
|
}
|
|
535
|
+
var API_CLIENT_CANDIDATES = [
|
|
536
|
+
"lib/api.ts",
|
|
537
|
+
"lib/api.tsx",
|
|
538
|
+
"lib/api.js",
|
|
539
|
+
"lib/api.jsx",
|
|
540
|
+
"lib/api-client.ts",
|
|
541
|
+
"lib/api-client.tsx",
|
|
542
|
+
"lib/api-client.js",
|
|
543
|
+
"lib/api-client.jsx",
|
|
544
|
+
"src/lib/api.ts",
|
|
545
|
+
"src/lib/api.tsx",
|
|
546
|
+
"src/lib/api.js",
|
|
547
|
+
"src/lib/api.jsx",
|
|
548
|
+
"src/lib/api-client.ts",
|
|
549
|
+
"src/lib/api-client.tsx",
|
|
550
|
+
"src/lib/api-client.js",
|
|
551
|
+
"src/lib/api-client.jsx",
|
|
552
|
+
"services/api.ts",
|
|
553
|
+
"services/api.js",
|
|
554
|
+
"services/api.tsx",
|
|
555
|
+
"services/api.jsx",
|
|
556
|
+
"src/services/api.ts",
|
|
557
|
+
"src/services/api.js",
|
|
558
|
+
"src/services/api.tsx",
|
|
559
|
+
"src/services/api.jsx",
|
|
560
|
+
"utils/api.ts",
|
|
561
|
+
"utils/api.js",
|
|
562
|
+
"utils/api.tsx",
|
|
563
|
+
"utils/api.jsx",
|
|
564
|
+
"src/utils/api.ts",
|
|
565
|
+
"src/utils/api.js",
|
|
566
|
+
"src/utils/api.tsx",
|
|
567
|
+
"src/utils/api.jsx"
|
|
568
|
+
];
|
|
569
|
+
function hasApiClientPatterns(source) {
|
|
570
|
+
return /\.(get|post|put|delete|patch)\s*\(/.test(source) || /\bfetch\s*\(/.test(source) || /\baxios\b/.test(source) || /\bbaseURL\b/.test(source) || /\bAPI_URL\b/.test(source);
|
|
571
|
+
}
|
|
572
|
+
async function findApiClientEagerly(projectRoot, alreadyIncluded) {
|
|
573
|
+
for (const candidate of API_CLIENT_CANDIDATES) {
|
|
574
|
+
const fullPath = join3(projectRoot, candidate);
|
|
575
|
+
if (alreadyIncluded.has(fullPath)) continue;
|
|
576
|
+
if (!existsSync2(fullPath)) continue;
|
|
577
|
+
try {
|
|
578
|
+
const content = await readFile3(fullPath, "utf-8");
|
|
579
|
+
if (hasApiClientPatterns(content)) {
|
|
580
|
+
return { path: fullPath, content };
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
457
587
|
async function analyzePage(route, options) {
|
|
458
588
|
const { projectRoot, maxTokensPerPage = DEFAULT_MAX_TOKENS } = options;
|
|
459
589
|
const maxChars = maxTokensPerPage * CHARS_PER_TOKEN;
|
|
@@ -473,6 +603,39 @@ ${content}
|
|
|
473
603
|
}
|
|
474
604
|
sourceContext += section;
|
|
475
605
|
}
|
|
606
|
+
const alreadyIncluded = new Set(tracedFiles.keys());
|
|
607
|
+
let apiClientPath = null;
|
|
608
|
+
let apiClientSource = null;
|
|
609
|
+
const eagerResult = await findApiClientEagerly(projectRoot, alreadyIncluded);
|
|
610
|
+
if (eagerResult) {
|
|
611
|
+
apiClientPath = eagerResult.path;
|
|
612
|
+
apiClientSource = eagerResult.content;
|
|
613
|
+
const relativePath = eagerResult.path.startsWith(projectRoot) ? eagerResult.path.slice(projectRoot.length + 1) : eagerResult.path;
|
|
614
|
+
const section = `
|
|
615
|
+
// === ${relativePath} ===
|
|
616
|
+
${eagerResult.content}
|
|
617
|
+
`;
|
|
618
|
+
if (sourceContext.length + section.length <= maxChars) {
|
|
619
|
+
sourceContext += section;
|
|
620
|
+
}
|
|
621
|
+
resolvedImports.push(eagerResult.path);
|
|
622
|
+
allSources.push(eagerResult.content);
|
|
623
|
+
} else {
|
|
624
|
+
for (const [filePath, content] of tracedFiles) {
|
|
625
|
+
if (filePath === route.filePath) continue;
|
|
626
|
+
if (hasApiClientPatterns(content)) {
|
|
627
|
+
apiClientPath = filePath;
|
|
628
|
+
apiClientSource = content;
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
let extractedEndpointsList = [];
|
|
634
|
+
let apiBaseUrl = null;
|
|
635
|
+
if (apiClientSource) {
|
|
636
|
+
extractedEndpointsList = extractEndpoints(apiClientSource);
|
|
637
|
+
apiBaseUrl = extractBaseUrl(apiClientSource);
|
|
638
|
+
}
|
|
476
639
|
const authConfig = await detectAuth(projectRoot, allSources);
|
|
477
640
|
const combinedSource = allSources.join("\n");
|
|
478
641
|
const hasApi = hasApiCalls(sourceContext) || hasApiCalls(combinedSource);
|
|
@@ -490,13 +653,13 @@ ${content}
|
|
|
490
653
|
unresolvedImports: [...new Set(unresolvedImports)],
|
|
491
654
|
authConfig,
|
|
492
655
|
hasApiDependencies: hasApi,
|
|
493
|
-
estimatedTokens: estimateTokens(sourceContext)
|
|
656
|
+
estimatedTokens: estimateTokens(sourceContext),
|
|
657
|
+
apiClientPath,
|
|
658
|
+
extractedEndpoints: extractedEndpointsList,
|
|
659
|
+
apiBaseUrl
|
|
494
660
|
};
|
|
495
661
|
}
|
|
496
662
|
|
|
497
|
-
// ../core/src/mock/types.ts
|
|
498
|
-
var ALL_STATE_VARIANTS = ["success", "empty", "error", "loading"];
|
|
499
|
-
|
|
500
663
|
// ../core/src/mock/llm-client.ts
|
|
501
664
|
import Anthropic from "@anthropic-ai/sdk";
|
|
502
665
|
var LlmClient = class {
|
|
@@ -524,20 +687,18 @@ var LlmClient = class {
|
|
|
524
687
|
};
|
|
525
688
|
|
|
526
689
|
// ../core/src/mock/prompt-templates.ts
|
|
527
|
-
var SYSTEM_PROMPT = `You are a mock data generator for a UI pre-rendering tool called
|
|
528
|
-
Your job is to analyze a Next.js page's source code and generate realistic mock API responses that will make the page render correctly.
|
|
690
|
+
var SYSTEM_PROMPT = `You are a mock data generator for a UI pre-rendering tool called Code to Design.
|
|
691
|
+
Your job is to analyze a Next.js page's source code and generate realistic mock API responses that will make the page render correctly in different visual states.
|
|
529
692
|
|
|
530
693
|
Rules:
|
|
531
694
|
1. Output ONLY valid JSON \u2014 no markdown, no code fences, no explanation.
|
|
532
|
-
2. Mock data must match the TypeScript interfaces found in the source code.
|
|
695
|
+
2. Mock data must match the TypeScript interfaces and API response shapes found in the source code.
|
|
533
696
|
3. Generate contextually appropriate data (e.g., real company names for a finance app, realistic usernames for a social app).
|
|
534
|
-
4.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
5. Include auth mock data if the page requires authentication.
|
|
540
|
-
6. For dynamic route parameters, generate a realistic sample value.`;
|
|
697
|
+
4. Generate MULTIPLE items in success data (at least 3-5 items for list endpoints).
|
|
698
|
+
5. If actual API endpoint URLs are provided, use those EXACT paths in your response.
|
|
699
|
+
6. Analyze the page's conditional rendering (if/else, ternary, switch, state variables) to identify meaningful visual states \u2014 not just generic success/error.
|
|
700
|
+
7. Include auth mock data if the page requires authentication.
|
|
701
|
+
8. For dynamic route parameters, generate a realistic sample value.`;
|
|
541
702
|
function buildUserPrompt(analysis) {
|
|
542
703
|
const parts = [];
|
|
543
704
|
parts.push(`## Page Route: ${analysis.route.urlPath}`);
|
|
@@ -545,6 +706,16 @@ function buildUserPrompt(analysis) {
|
|
|
545
706
|
if (analysis.route.isDynamic) {
|
|
546
707
|
parts.push(`## Dynamic Parameters: ${analysis.route.params.map((p) => p.name).join(", ")}`);
|
|
547
708
|
}
|
|
709
|
+
if (analysis.extractedEndpoints.length > 0) {
|
|
710
|
+
parts.push("## Actual API Endpoints (use these EXACT paths)");
|
|
711
|
+
for (const ep of analysis.extractedEndpoints) {
|
|
712
|
+
const name = ep.functionName ? ` (${ep.functionName})` : "";
|
|
713
|
+
parts.push(`- ${ep.method} ${ep.path}${name}`);
|
|
714
|
+
}
|
|
715
|
+
if (analysis.apiBaseUrl) {
|
|
716
|
+
parts.push(`- Base URL: ${analysis.apiBaseUrl}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
548
719
|
parts.push("## Source Code Context");
|
|
549
720
|
parts.push(analysis.sourceContext);
|
|
550
721
|
if (analysis.authConfig.hasAuth) {
|
|
@@ -563,29 +734,51 @@ function buildUserPrompt(analysis) {
|
|
|
563
734
|
parts.push(`
|
|
564
735
|
## Required Output Format
|
|
565
736
|
|
|
737
|
+
Analyze the page code carefully. Look at:
|
|
738
|
+
- Conditional rendering (if/else, ternary operators, switch statements)
|
|
739
|
+
- State variables that control what is displayed
|
|
740
|
+
- URL parameters or search params that affect the view
|
|
741
|
+
- Authentication state checks
|
|
742
|
+
- Data loading patterns
|
|
743
|
+
|
|
744
|
+
Then generate mock data for **page-specific visual states** \u2014 not just generic success/error. For example:
|
|
745
|
+
- A dashboard with tabs might have states: "overview_tab", "readings_tab", "settings_tab"
|
|
746
|
+
- A page checking auth might have: "authenticated_with_data", "authenticated_empty", "unauthenticated"
|
|
747
|
+
- A results page might have: "results_found", "no_results", "invalid_id"
|
|
748
|
+
|
|
749
|
+
Always include at least these base states:
|
|
750
|
+
- One state with realistic populated data (the "happy path")
|
|
751
|
+
- One state with empty/no data
|
|
752
|
+
- One error state (API returns 500)
|
|
753
|
+
|
|
566
754
|
Return a JSON object with this exact structure:
|
|
567
755
|
{
|
|
568
|
-
"routeParams": { "paramName": "sampleValue" },
|
|
569
|
-
"
|
|
756
|
+
"routeParams": { "paramName": "sampleValue" },
|
|
757
|
+
"stateVariants": [
|
|
570
758
|
{
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"
|
|
574
|
-
"
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
759
|
+
"name": "descriptive_state_name",
|
|
760
|
+
"description": "What this state represents visually",
|
|
761
|
+
"apiMocks": {
|
|
762
|
+
"METHOD /endpoint/path": {
|
|
763
|
+
"status": 200,
|
|
764
|
+
"body": { ... },
|
|
765
|
+
"delay": 0
|
|
766
|
+
}
|
|
578
767
|
}
|
|
579
768
|
}
|
|
580
769
|
],
|
|
581
|
-
"authMock": {
|
|
770
|
+
"authMock": {
|
|
582
771
|
"cookies": { "cookie_name": "mock_value" },
|
|
583
772
|
"authCheckEndpoint": "/auth/me",
|
|
584
773
|
"authCheckResponse": { "status": 200, "body": { ... } }
|
|
585
774
|
}
|
|
586
775
|
}
|
|
587
776
|
|
|
588
|
-
|
|
777
|
+
IMPORTANT:
|
|
778
|
+
- Use the ACTUAL endpoint paths from the "Actual API Endpoints" section above if provided.
|
|
779
|
+
- Each state variant should produce a VISUALLY DIFFERENT page render.
|
|
780
|
+
- Generate at least 3-5 items in list/array responses for the happy path state.
|
|
781
|
+
- Generate ONLY the JSON. No explanation, no markdown fences.`);
|
|
589
782
|
return parts.join("\n\n");
|
|
590
783
|
}
|
|
591
784
|
|
|
@@ -609,7 +802,41 @@ function parseLlmJson(content) {
|
|
|
609
802
|
return null;
|
|
610
803
|
}
|
|
611
804
|
}
|
|
612
|
-
function
|
|
805
|
+
function isV2Output(output) {
|
|
806
|
+
return "stateVariants" in output && Array.isArray(output.stateVariants);
|
|
807
|
+
}
|
|
808
|
+
function convertV2ToMockConfigs(output, analysis) {
|
|
809
|
+
if (!output.stateVariants || output.stateVariants.length === 0) {
|
|
810
|
+
return [];
|
|
811
|
+
}
|
|
812
|
+
return output.stateVariants.map((variant) => {
|
|
813
|
+
const apiMocks = {};
|
|
814
|
+
for (const [pattern, mock] of Object.entries(variant.apiMocks)) {
|
|
815
|
+
apiMocks[pattern] = {
|
|
816
|
+
status: mock.status,
|
|
817
|
+
body: mock.body,
|
|
818
|
+
delay: mock.delay
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
let authMock = null;
|
|
822
|
+
if (analysis.authConfig.hasAuth && output.authMock) {
|
|
823
|
+
authMock = {
|
|
824
|
+
cookies: output.authMock.cookies,
|
|
825
|
+
authCheckResponse: {
|
|
826
|
+
status: output.authMock.authCheckResponse.status,
|
|
827
|
+
body: output.authMock.authCheckResponse.body
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
stateName: variant.name,
|
|
833
|
+
apiMocks,
|
|
834
|
+
authMock,
|
|
835
|
+
routeParams: output.routeParams
|
|
836
|
+
};
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
function convertV1ToMockConfigs(output, analysis, variants) {
|
|
613
840
|
const configs = [];
|
|
614
841
|
for (const variant of variants) {
|
|
615
842
|
const apiMocks = {};
|
|
@@ -659,7 +886,7 @@ function generateFallbackConfigs(analysis, variants) {
|
|
|
659
886
|
}));
|
|
660
887
|
}
|
|
661
888
|
async function generateMocks(analysis, options) {
|
|
662
|
-
const variants = options.variants ??
|
|
889
|
+
const variants = options.variants ?? ["success", "empty", "error", "loading"];
|
|
663
890
|
if (!analysis.hasApiDependencies) {
|
|
664
891
|
return {
|
|
665
892
|
configs: generateFallbackConfigs(analysis, variants),
|
|
@@ -678,7 +905,15 @@ async function generateMocks(analysis, options) {
|
|
|
678
905
|
tokenUsage: { input: response.inputTokens, output: response.outputTokens }
|
|
679
906
|
};
|
|
680
907
|
}
|
|
681
|
-
|
|
908
|
+
let configs;
|
|
909
|
+
if (isV2Output(parsed)) {
|
|
910
|
+
configs = convertV2ToMockConfigs(parsed, analysis);
|
|
911
|
+
} else {
|
|
912
|
+
configs = convertV1ToMockConfigs(parsed, analysis, variants);
|
|
913
|
+
}
|
|
914
|
+
if (configs.length === 0) {
|
|
915
|
+
configs = generateFallbackConfigs(analysis, variants);
|
|
916
|
+
}
|
|
682
917
|
return {
|
|
683
918
|
configs,
|
|
684
919
|
tokenUsage: { input: response.inputTokens, output: response.outputTokens }
|
|
@@ -965,6 +1200,32 @@ async function captureInteractions(page, pageUrl, route, stateName, outputDir, o
|
|
|
965
1200
|
return results;
|
|
966
1201
|
}
|
|
967
1202
|
|
|
1203
|
+
// ../core/src/render/url-matcher.ts
|
|
1204
|
+
function matchMockUrl(requestUrl, requestMethod, mockPattern) {
|
|
1205
|
+
const [mockMethod, ...pathParts] = mockPattern.split(" ");
|
|
1206
|
+
if (requestMethod !== mockMethod) return false;
|
|
1207
|
+
let mockPath = pathParts.join(" ");
|
|
1208
|
+
mockPath = mockPath.replace(/\/+$/, "");
|
|
1209
|
+
const urlPath = new URL(requestUrl).pathname.replace(/\/+$/, "");
|
|
1210
|
+
const regexStr = mockPath.replace(/\{[^}]+\}/g, "[^/]+").replace(/\*/g, "[^/]+").replace(/:[a-zA-Z_]+/g, "[^/]+");
|
|
1211
|
+
const fullRegex = new RegExp("^" + regexStr.replace(/\//g, "\\/") + "$");
|
|
1212
|
+
if (fullRegex.test(urlPath)) return true;
|
|
1213
|
+
const mockSegments = mockPath.split("/").filter(Boolean);
|
|
1214
|
+
const urlSegments = urlPath.split("/").filter(Boolean);
|
|
1215
|
+
if (mockSegments.length >= 1 && urlSegments.length >= mockSegments.length) {
|
|
1216
|
+
const urlSuffix = urlSegments.slice(-mockSegments.length);
|
|
1217
|
+
const match = mockSegments.every((seg, i) => {
|
|
1218
|
+
if (seg.startsWith("{") || seg.startsWith(":") || seg === "*") return true;
|
|
1219
|
+
return seg === urlSuffix[i];
|
|
1220
|
+
});
|
|
1221
|
+
if (match) return true;
|
|
1222
|
+
}
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
function isApiUrl(url) {
|
|
1226
|
+
return url.includes("/api/") || url.includes(":8000") || url.includes(":3000/api");
|
|
1227
|
+
}
|
|
1228
|
+
|
|
968
1229
|
// ../core/src/render/pre-renderer.ts
|
|
969
1230
|
var DEFAULT_CONCURRENCY = 3;
|
|
970
1231
|
var DEFAULT_PAGE_TIMEOUT = 15e3;
|
|
@@ -1004,6 +1265,34 @@ async function setupMockInterception(page, devServerUrl, mockConfig, authConfig)
|
|
|
1004
1265
|
}));
|
|
1005
1266
|
await page.context().addCookies(cookies);
|
|
1006
1267
|
}
|
|
1268
|
+
if (authConfig.hasAuth) {
|
|
1269
|
+
const mockUser = mockConfig.authMock?.authCheckResponse?.body ?? {
|
|
1270
|
+
id: "mock_user",
|
|
1271
|
+
email: "demo@c2d.dev",
|
|
1272
|
+
name: "Demo User"
|
|
1273
|
+
};
|
|
1274
|
+
const mockToken = "c2d_mock_jwt_token";
|
|
1275
|
+
await page.goto(devServerUrl, { waitUntil: "commit", timeout: 1e4 }).catch(() => {
|
|
1276
|
+
});
|
|
1277
|
+
await page.evaluate(`(() => {
|
|
1278
|
+
// Zustand persist pattern (auth-storage)
|
|
1279
|
+
const zustandState = {
|
|
1280
|
+
state: {
|
|
1281
|
+
user: ${JSON.stringify(mockUser)},
|
|
1282
|
+
tokens: { access: "${mockToken}", refresh: "${mockToken}" },
|
|
1283
|
+
isAuthenticated: true,
|
|
1284
|
+
isLoading: false,
|
|
1285
|
+
},
|
|
1286
|
+
version: 0,
|
|
1287
|
+
};
|
|
1288
|
+
localStorage.setItem('auth-storage', JSON.stringify(zustandState));
|
|
1289
|
+
|
|
1290
|
+
// Common alternatives
|
|
1291
|
+
localStorage.setItem('token', '"${mockToken}"');
|
|
1292
|
+
localStorage.setItem('access_token', '"${mockToken}"');
|
|
1293
|
+
localStorage.setItem('user', JSON.stringify(${JSON.stringify(mockUser)}));
|
|
1294
|
+
})()`);
|
|
1295
|
+
}
|
|
1007
1296
|
const authMockBody = mockConfig.authMock?.authCheckResponse ? JSON.stringify(mockConfig.authMock.authCheckResponse.body) : JSON.stringify({ id: "mock_user", email: "demo@c2d.dev", name: "Demo User", email_verified: true, avatar_url: null, created_at: "2026-01-01T00:00:00Z" });
|
|
1008
1297
|
const AUTH_PATTERNS = ["/auth/me", "/auth/session", "/api/auth/me", "/api/user/me", "/api/auth/session"];
|
|
1009
1298
|
await page.route("**/*", async (route) => {
|
|
@@ -1020,12 +1309,7 @@ async function setupMockInterception(page, devServerUrl, mockConfig, authConfig)
|
|
|
1020
1309
|
});
|
|
1021
1310
|
}
|
|
1022
1311
|
for (const [pattern, mock] of Object.entries(mockConfig.apiMocks)) {
|
|
1023
|
-
|
|
1024
|
-
let mockPath = pathParts.join(" ");
|
|
1025
|
-
mockPath = mockPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
1026
|
-
mockPath = mockPath.replace(/\*/g, "[^/]+");
|
|
1027
|
-
const pathRegex = new RegExp(mockPath.replace(/\//g, "\\/"));
|
|
1028
|
-
if (method === mockMethod && pathRegex.test(url)) {
|
|
1312
|
+
if (matchMockUrl(url, method, pattern)) {
|
|
1029
1313
|
if (mock.delay) await new Promise((r) => setTimeout(r, mock.delay));
|
|
1030
1314
|
return route.fulfill({
|
|
1031
1315
|
status: mock.status,
|
|
@@ -1034,6 +1318,13 @@ async function setupMockInterception(page, devServerUrl, mockConfig, authConfig)
|
|
|
1034
1318
|
});
|
|
1035
1319
|
}
|
|
1036
1320
|
}
|
|
1321
|
+
if (isApiUrl(url)) {
|
|
1322
|
+
return route.fulfill({
|
|
1323
|
+
status: 200,
|
|
1324
|
+
contentType: "application/json",
|
|
1325
|
+
body: JSON.stringify({ data: [], message: "ok" })
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1037
1328
|
return route.continue();
|
|
1038
1329
|
});
|
|
1039
1330
|
}
|
|
@@ -1888,4 +2179,4 @@ export {
|
|
|
1888
2179
|
detectNextJsProject,
|
|
1889
2180
|
runScan
|
|
1890
2181
|
};
|
|
1891
|
-
//# sourceMappingURL=chunk-
|
|
2182
|
+
//# sourceMappingURL=chunk-3CJ7RVFI.js.map
|