@autoview/cli 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/LICENSE +661 -0
- package/README.md +407 -0
- package/lib/AutoViewAgent.d.ts +109 -0
- package/lib/AutoViewAgent.js +123 -0
- package/lib/AutoViewAgent.js.map +1 -0
- package/lib/agent/emitMcpServer.d.ts +15 -0
- package/lib/agent/emitMcpServer.js +157 -0
- package/lib/agent/emitMcpServer.js.map +1 -0
- package/lib/agent/emitReport.d.ts +14 -0
- package/lib/agent/emitReport.js +85 -0
- package/lib/agent/emitReport.js.map +1 -0
- package/lib/agent/toolSurface.d.ts +130 -0
- package/lib/agent/toolSurface.js +342 -0
- package/lib/agent/toolSurface.js.map +1 -0
- package/lib/agent/verifyAgentTasks.d.ts +87 -0
- package/lib/agent/verifyAgentTasks.js +126 -0
- package/lib/agent/verifyAgentTasks.js.map +1 -0
- package/lib/cli/main.d.ts +2 -0
- package/lib/cli/main.js +295 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
- package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
- package/lib/constants/AutoViewFrontendTemplate.js +46 -0
- package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
- package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
- package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
- package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
- package/lib/context/IAutoViewAgentContext.d.ts +60 -0
- package/lib/context/IAutoViewAgentContext.js +3 -0
- package/lib/context/IAutoViewAgentContext.js.map +1 -0
- package/lib/fromSwagger.d.ts +53 -0
- package/lib/fromSwagger.js +513 -0
- package/lib/fromSwagger.js.map +1 -0
- package/lib/generateDeterministic.d.ts +26 -0
- package/lib/generateDeterministic.js +75 -0
- package/lib/generateDeterministic.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
- package/lib/orchestrate/orchestrateAutoView.js +491 -0
- package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
- package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
- package/lib/orchestrate/utils/HistoryMessage.js +25 -0
- package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
- package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
- package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
- package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
- package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
- package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
- package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
- package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
- package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
- package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
- package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
- package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
- package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
- package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
- package/lib/preview/renderPreview.d.ts +22 -0
- package/lib/preview/renderPreview.js +198 -0
- package/lib/preview/renderPreview.js.map +1 -0
- package/lib/typings/compiler.d.ts +39 -0
- package/lib/typings/compiler.js +3 -0
- package/lib/typings/compiler.js.map +1 -0
- package/lib/typings/events.d.ts +106 -0
- package/lib/typings/events.js +3 -0
- package/lib/typings/events.js.map +1 -0
- package/lib/typings/index.d.ts +10 -0
- package/lib/typings/index.js +27 -0
- package/lib/typings/index.js.map +1 -0
- package/lib/typings/misc.d.ts +78 -0
- package/lib/typings/misc.js +3 -0
- package/lib/typings/misc.js.map +1 -0
- package/lib/utils/ArrayUtil.d.ts +8 -0
- package/lib/utils/ArrayUtil.js +30 -0
- package/lib/utils/ArrayUtil.js.map +1 -0
- package/lib/utils/StringUtil.d.ts +11 -0
- package/lib/utils/StringUtil.js +28 -0
- package/lib/utils/StringUtil.js.map +1 -0
- package/lib/utils/classifyEndpoints.d.ts +62 -0
- package/lib/utils/classifyEndpoints.js +216 -0
- package/lib/utils/classifyEndpoints.js.map +1 -0
- package/lib/utils/endpointFilter.d.ts +26 -0
- package/lib/utils/endpointFilter.js +0 -0
- package/lib/utils/endpointFilter.js.map +1 -0
- package/lib/utils/extractFields.d.ts +85 -0
- package/lib/utils/extractFields.js +231 -0
- package/lib/utils/extractFields.js.map +1 -0
- package/lib/utils/index.d.ts +13 -0
- package/lib/utils/index.js +30 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/normalizeForNestia.d.ts +34 -0
- package/lib/utils/normalizeForNestia.js +133 -0
- package/lib/utils/normalizeForNestia.js.map +1 -0
- package/lib/utils/resourcePlan.d.ts +39 -0
- package/lib/utils/resourcePlan.js +95 -0
- package/lib/utils/resourcePlan.js.map +1 -0
- package/lib/utils/sliceDocument.d.ts +17 -0
- package/lib/utils/sliceDocument.js +114 -0
- package/lib/utils/sliceDocument.js.map +1 -0
- package/lib/utils/toEndpoints.d.ts +90 -0
- package/lib/utils/toEndpoints.js +227 -0
- package/lib/utils/toEndpoints.js.map +1 -0
- package/lib/verify/runWorkflows.d.ts +25 -0
- package/lib/verify/runWorkflows.js +366 -0
- package/lib/verify/runWorkflows.js.map +1 -0
- package/lib/verify/workflows.d.ts +53 -0
- package/lib/verify/workflows.js +107 -0
- package/lib/verify/workflows.js.map +1 -0
- package/package.json +82 -0
- package/prompts/AUTOVIEW_RENDER.md +398 -0
- package/prompts/AUTOVIEW_REVIEW.md +60 -0
- package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
- package/src/AutoViewAgent.ts +222 -0
- package/src/agent/emitMcpServer.integration.test.ts +168 -0
- package/src/agent/emitMcpServer.test.ts +51 -0
- package/src/agent/emitMcpServer.ts +178 -0
- package/src/agent/emitReport.ts +117 -0
- package/src/agent/toolSurface.test.ts +243 -0
- package/src/agent/toolSurface.ts +501 -0
- package/src/agent/verifyAgentTasks.test.ts +106 -0
- package/src/agent/verifyAgentTasks.ts +171 -0
- package/src/cli/main.ts +363 -0
- package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
- package/src/constants/AutoViewFrontendTemplate.ts +42 -0
- package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
- package/src/context/IAutoViewAgentContext.ts +84 -0
- package/src/fromSwagger.test.ts +269 -0
- package/src/fromSwagger.ts +500 -0
- package/src/generateDeterministic.test.ts +39 -0
- package/src/generateDeterministic.ts +77 -0
- package/src/index.ts +30 -0
- package/src/orchestrate/orchestrateAutoView.ts +590 -0
- package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
- package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
- package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
- package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
- package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
- package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
- package/src/orchestrate/renderNavTs.test.ts +74 -0
- package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
- package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
- package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
- package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
- package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
- package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
- package/src/orchestrate/utils/HistoryMessage.ts +41 -0
- package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
- package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
- package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
- package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
- package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
- package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
- package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
- package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
- package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
- package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
- package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
- package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
- package/src/preview/renderPreview.ts +273 -0
- package/src/typings/compiler.ts +47 -0
- package/src/typings/events.ts +155 -0
- package/src/typings/index.ts +10 -0
- package/src/typings/misc.ts +93 -0
- package/src/utils/ArrayUtil.ts +16 -0
- package/src/utils/StringUtil.ts +29 -0
- package/src/utils/classifyEndpoints.test.ts +86 -0
- package/src/utils/classifyEndpoints.ts +291 -0
- package/src/utils/endpointFilter.test.ts +50 -0
- package/src/utils/endpointFilter.ts +0 -0
- package/src/utils/extractFields.test.ts +82 -0
- package/src/utils/extractFields.ts +306 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/normalizeForNestia.test.ts +93 -0
- package/src/utils/normalizeForNestia.ts +139 -0
- package/src/utils/resourcePlan.test.ts +104 -0
- package/src/utils/resourcePlan.ts +180 -0
- package/src/utils/sliceDocument.test.ts +85 -0
- package/src/utils/sliceDocument.ts +119 -0
- package/src/utils/toEndpoints.test.ts +251 -0
- package/src/utils/toEndpoints.ts +343 -0
- package/src/verify/runWorkflows.ts +403 -0
- package/src/verify/workflows.test.ts +117 -0
- package/src/verify/workflows.ts +154 -0
- package/template/CLAUDE.md +140 -0
- package/template/Dockerfile +31 -0
- package/template/PROMPT.md +80 -0
- package/template/SANDBOX.md +70 -0
- package/template/app/api/health/route.ts +10 -0
- package/template/app/globals.css +97 -0
- package/template/app/layout.tsx +30 -0
- package/template/app/page.tsx +19 -0
- package/template/components/AppShell.tsx +114 -0
- package/template/components/auto/CatalogGrid.tsx +159 -0
- package/template/components/auto/ConfirmButton.tsx +67 -0
- package/template/components/auto/EmbeddedCollection.tsx +144 -0
- package/template/components/auto/ResourceDashboard.tsx +104 -0
- package/template/components/auto/ResourceDetail.tsx +93 -0
- package/template/components/auto/ResourceForm.tsx +235 -0
- package/template/components/auto/ResourceIcon.tsx +88 -0
- package/template/components/auto/ResourceLanding.tsx +155 -0
- package/template/components/auto/ResourceTable.tsx +223 -0
- package/template/components/auto/formatValue.tsx +186 -0
- package/template/components/auto/types.ts +42 -0
- package/template/components/ui/badge.tsx +40 -0
- package/template/components/ui/button.tsx +57 -0
- package/template/components/ui/card.tsx +86 -0
- package/template/components/ui/dialog.tsx +119 -0
- package/template/components/ui/input.tsx +23 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/pagination.tsx +117 -0
- package/template/components/ui/select.tsx +92 -0
- package/template/components/ui/sheet.tsx +135 -0
- package/template/components/ui/skeleton.tsx +15 -0
- package/template/components/ui/table.tsx +120 -0
- package/template/components/ui/tabs.tsx +55 -0
- package/template/lib/utils.ts +35 -0
- package/template/next.config.mjs +52 -0
- package/template/package.json +46 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/start-shopping-backend.sh +56 -0
- package/template/tailwind.config.ts +96 -0
- package/template/tsconfig.json +29 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
import {
|
|
3
|
+
AutoBeAutoViewScaffoldEvent,
|
|
4
|
+
IAutoBeCompiler,
|
|
5
|
+
} from "../typings";
|
|
6
|
+
import { v7 } from "uuid";
|
|
7
|
+
|
|
8
|
+
import { AutoViewFrontendTemplate } from "../constants/AutoViewFrontendTemplate";
|
|
9
|
+
import { IAutoViewAgentContext } from "../context/IAutoViewAgentContext";
|
|
10
|
+
import { IAutoViewProductPlan } from "./structures/IAutoViewProductPlan";
|
|
11
|
+
import { IAutoViewSdkMap } from "./structures/IAutoViewSdkMap";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Phase 3 of the AutoView agent — Scaffold.
|
|
15
|
+
*
|
|
16
|
+
* Deterministic. No LLM calls. Produces the file layout of a runnable Next.js +
|
|
17
|
+
* shadcn/ui project shaped for the in-house Sandbox platform:
|
|
18
|
+
*
|
|
19
|
+
* - Copies the bundled frontend pack (`internals/template/frontend/`) verbatim —
|
|
20
|
+
* Dockerfile, configs, shadcn/ui primitives, base app shell, health route.
|
|
21
|
+
* - Adds the AutoBE-generated SDK under `src/api/**`.
|
|
22
|
+
* - Adds an SDK adapter and a mock `IConnection` so generated pages can import a
|
|
23
|
+
* single helper instead of touching the SDK directly.
|
|
24
|
+
* - Materializes one placeholder TSX page per screen in
|
|
25
|
+
* {@link IAutoViewProductPlan} so the project compiles without Phase 4 having
|
|
26
|
+
* run yet; Phase 4 (Render) overwrites these in place.
|
|
27
|
+
* - Drops the Phase 1 and Phase 2 markdown into `wiki/` so the operator can audit
|
|
28
|
+
* the agent's intent alongside the code.
|
|
29
|
+
*/
|
|
30
|
+
export async function orchestrateAutoViewScaffold(
|
|
31
|
+
ctx: IAutoViewAgentContext,
|
|
32
|
+
props: {
|
|
33
|
+
document: OpenApi.IDocument;
|
|
34
|
+
sdkMap: IAutoViewSdkMap;
|
|
35
|
+
sdkMapMarkdown: string;
|
|
36
|
+
productPlan: IAutoViewProductPlan;
|
|
37
|
+
productPlanMarkdown: string;
|
|
38
|
+
step: number;
|
|
39
|
+
/**
|
|
40
|
+
* Backend hint from the orchestrator. When the source carries a real server
|
|
41
|
+
* URL (shopping fixture), scaffold a live-mode connection so the dev server
|
|
42
|
+
* renders real data. When `host` is `null` (AutoBE interface phase), fall
|
|
43
|
+
* back to simulate-only.
|
|
44
|
+
*/
|
|
45
|
+
backend: { host: string | null };
|
|
46
|
+
},
|
|
47
|
+
): Promise<Record<string, string>> {
|
|
48
|
+
const files: Record<string, string> = {};
|
|
49
|
+
|
|
50
|
+
// 1. Bundled frontend pack — Dockerfile, configs, shadcn/ui base, app
|
|
51
|
+
// shell, health route, CLAUDE.md / PROMPT.md / SANDBOX.md.
|
|
52
|
+
for (const [key, value] of Object.entries(AutoViewFrontendTemplate)) {
|
|
53
|
+
files[key] = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. SDK under `src/api/**` from the AutoBE interface compiler.
|
|
57
|
+
const compiler: IAutoBeCompiler = await ctx.compiler();
|
|
58
|
+
const written: Record<string, string> = await compiler.interface.write(
|
|
59
|
+
props.document,
|
|
60
|
+
[],
|
|
61
|
+
);
|
|
62
|
+
for (const [key, value] of Object.entries(written)) {
|
|
63
|
+
if (key.startsWith("src/api/")) {
|
|
64
|
+
files[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. SDK adapter + mock connection. Generated pages import from
|
|
69
|
+
// `@/lib/api` and `@/lib/connection` instead of touching the raw SDK,
|
|
70
|
+
// so the Render phase has one stable shape to talk to.
|
|
71
|
+
files["src/lib/connection.ts"] = renderConnectionTs(props.backend);
|
|
72
|
+
files["src/lib/api.ts"] = SDK_ADAPTER_TS;
|
|
73
|
+
|
|
74
|
+
// BootstrapGate + root layout override. The gate runs `bootstrapAuth()`
|
|
75
|
+
// once on mount before any page-level SDK call fires, so individual
|
|
76
|
+
// pages do not need to know about auth. When the bootstrap is a
|
|
77
|
+
// no-op (simulate / no-backend variant) children render immediately.
|
|
78
|
+
// The template ships a layout.tsx that does not wire the gate;
|
|
79
|
+
// overriding it here keeps the gate change in one place.
|
|
80
|
+
files["src/components/BootstrapGate.tsx"] = BOOTSTRAP_GATE_TSX;
|
|
81
|
+
files["app/layout.tsx"] = ROOT_LAYOUT_TSX;
|
|
82
|
+
|
|
83
|
+
// Navigation config consumed by the AppShell sidebar — generated from the
|
|
84
|
+
// top-level resources so the chrome reflects this swagger's domain.
|
|
85
|
+
files["src/lib/nav.ts"] = renderNavTs(props.document, props.productPlan);
|
|
86
|
+
|
|
87
|
+
// 4. Placeholder TSX per planned screen. Keeps `tsc --noEmit` happy
|
|
88
|
+
// and Next.js routable even before Phase 4 has filled the pages in.
|
|
89
|
+
// Phase 4 (Render) overwrites these by path.
|
|
90
|
+
for (const screen of props.productPlan.screens) {
|
|
91
|
+
const filePath = screenPathToFile(screen.path);
|
|
92
|
+
if (filePath === null) continue;
|
|
93
|
+
files[filePath] = renderPlaceholderPage(screen);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 5. Wiki — Phase 1 + Phase 2 evidence next to the code.
|
|
97
|
+
files["wiki/sdk-map.md"] = props.sdkMapMarkdown;
|
|
98
|
+
files["wiki/product-plan.md"] = props.productPlanMarkdown;
|
|
99
|
+
|
|
100
|
+
ctx.dispatch({
|
|
101
|
+
type: "autoViewScaffold",
|
|
102
|
+
id: v7(),
|
|
103
|
+
created_at: new Date().toISOString(),
|
|
104
|
+
step: props.step,
|
|
105
|
+
files: Object.keys(files).length,
|
|
106
|
+
} satisfies AutoBeAutoViewScaffoldEvent);
|
|
107
|
+
|
|
108
|
+
return files;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* -------------------------------------------------------------------------- */
|
|
112
|
+
/* path → file mapping */
|
|
113
|
+
/* -------------------------------------------------------------------------- */
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Map a Next.js route path (`/catalog`, `/orders/[id]`) to the corresponding
|
|
117
|
+
* `app/.../page.tsx` location.
|
|
118
|
+
*
|
|
119
|
+
* Returns `null` for the bare `/` route — the bundled frontend pack already
|
|
120
|
+
* ships `app/page.tsx`, and Phase 4 overwrites it directly.
|
|
121
|
+
*/
|
|
122
|
+
function screenPathToFile(routePath: string): string | null {
|
|
123
|
+
const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
124
|
+
if (trimmed.length === 0) return "app/page.tsx";
|
|
125
|
+
return `app/${trimmed}/page.tsx`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function renderPlaceholderPage(screen: IAutoViewProductPlan.IScreen): string {
|
|
129
|
+
const title = jsonString(screen.title);
|
|
130
|
+
const purpose = jsonString(screen.purpose);
|
|
131
|
+
const pattern = jsonString(screen.uiPattern);
|
|
132
|
+
const actor = jsonString(screen.actor);
|
|
133
|
+
const endpointsArrayLit = JSON.stringify(screen.endpoints);
|
|
134
|
+
return `// Placeholder generated by AutoView Phase 3 (Scaffold).
|
|
135
|
+
// Phase 4 (Render) replaces this file with a real Next.js page composed of the
|
|
136
|
+
// SDK endpoints listed in the product plan. Until then the page renders a
|
|
137
|
+
// short summary so \`next build\` succeeds and the operator can navigate.
|
|
138
|
+
|
|
139
|
+
export default function Page() {
|
|
140
|
+
const endpoints: readonly string[] = ${endpointsArrayLit};
|
|
141
|
+
return (
|
|
142
|
+
<main className="container mx-auto py-10">
|
|
143
|
+
<p className="text-xs uppercase tracking-wide text-muted-foreground">
|
|
144
|
+
AutoView placeholder · {${pattern}}
|
|
145
|
+
</p>
|
|
146
|
+
<h1 className="mt-2 text-3xl font-bold tracking-tight">{${title}}</h1>
|
|
147
|
+
<p className="mt-3 text-muted-foreground">{${purpose}}</p>
|
|
148
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
149
|
+
Actor: <span className="font-mono">{${actor}}</span>
|
|
150
|
+
</p>
|
|
151
|
+
{endpoints.length > 0 ? (
|
|
152
|
+
<div className="mt-4 rounded-md border bg-muted/30 p-3">
|
|
153
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
154
|
+
Planned SDK endpoints
|
|
155
|
+
</p>
|
|
156
|
+
<ul className="mt-2 space-y-1 text-xs font-mono">
|
|
157
|
+
{endpoints.map((accessor) => (
|
|
158
|
+
<li key={accessor}>{accessor}</li>
|
|
159
|
+
))}
|
|
160
|
+
</ul>
|
|
161
|
+
</div>
|
|
162
|
+
) : null}
|
|
163
|
+
</main>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function jsonString(text: string): string {
|
|
170
|
+
return JSON.stringify(text);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* -------------------------------------------------------------------------- */
|
|
174
|
+
/* static helpers */
|
|
175
|
+
/* -------------------------------------------------------------------------- */
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Render the connection module.
|
|
179
|
+
*
|
|
180
|
+
* Two shapes:
|
|
181
|
+
*
|
|
182
|
+
* - When `backend.host` is non-null (shopping fixture or any SDK whose swagger
|
|
183
|
+
* declares `servers[]`), default to that host + `simulate: false` so pages
|
|
184
|
+
* render real product data on first boot. The operator can flip back to mocks
|
|
185
|
+
* via `NEXT_PUBLIC_API_SIMULATE=true`.
|
|
186
|
+
* - When `backend.host` is null (AutoBE interface phase — no live backend exists
|
|
187
|
+
* yet), default to `simulate: true` so the dev server boots without a real
|
|
188
|
+
* server behind it.
|
|
189
|
+
*/
|
|
190
|
+
function renderConnectionTs(backend: { host: string | null }): string {
|
|
191
|
+
if (backend.host !== null) {
|
|
192
|
+
return `import type { IConnection } from "@nestia/fetcher";
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Connection used by every generated page.
|
|
196
|
+
*
|
|
197
|
+
* Routed through Next.js's same-origin rewrite so the browser does not
|
|
198
|
+
* have to deal with the upstream backend's CORS policy — the samchon
|
|
199
|
+
* shopping backend, for instance, only advertises \`GET,HEAD,POST\` in
|
|
200
|
+
* its \`Access-Control-Allow-Methods\` response, which blocks every
|
|
201
|
+
* PATCH / PUT / DELETE preflight a typical catalog page issues. The
|
|
202
|
+
* rewrite lives in \`next.config.mjs\` and forwards \`/_backend/*\` to
|
|
203
|
+
* the real upstream (\`http://127.0.0.1:37001\` by default, see
|
|
204
|
+
* \`scripts/start-shopping-backend.sh\`). Override the upstream via
|
|
205
|
+
* \`NEXT_PUBLIC_API_HOST\` if you want to point elsewhere.
|
|
206
|
+
*
|
|
207
|
+
* Set \`NEXT_PUBLIC_API_SIMULATE=true\` to swap to typia-random mocks
|
|
208
|
+
* (the SDK then never hits the network).
|
|
209
|
+
*
|
|
210
|
+
* The \`Authorization\` header starts empty — \`bootstrapAuth()\` populates
|
|
211
|
+
* it on first call with a guest customer token issued by the shopping
|
|
212
|
+
* backend. \`BootstrapGate\` in the root layout invokes the bootstrap
|
|
213
|
+
* once on mount, before any page-level SDK call fires, so individual
|
|
214
|
+
* page components do not need to think about auth.
|
|
215
|
+
*/
|
|
216
|
+
/* -------------------------------------------------------------------------- */
|
|
217
|
+
/* connection scope */
|
|
218
|
+
/* -------------------------------------------------------------------------- */
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Which actor's token the SDK is currently authenticated as. The browser
|
|
222
|
+
* pathname picks the scope on every navigation — see \`BootstrapGate\`. A
|
|
223
|
+
* \`null\` scope means the bootstrap has not run yet.
|
|
224
|
+
*/
|
|
225
|
+
export type AuthScope = null | "customer" | "citizen" | "administrator" | "seller";
|
|
226
|
+
|
|
227
|
+
let activeScope: AuthScope = null;
|
|
228
|
+
const scopePromises: Partial<Record<Exclude<AuthScope, null>, Promise<void>>> = {};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Seed credentials for the samchon shopping backend. The bundled
|
|
232
|
+
* \`scripts/start-shopping-backend.sh\` always seeds the same single account,
|
|
233
|
+
* so these values are constants. \`SHOPPING_SYSTEM_PASSWORD\` defaults to
|
|
234
|
+
* \`samchon\` in the upstream \`.env\` (see seeders).
|
|
235
|
+
*/
|
|
236
|
+
const SEED_EMAIL = "robot@nestia.io";
|
|
237
|
+
const SEED_PASSWORD = "samchon";
|
|
238
|
+
const SEED_NICKNAME = "Robot";
|
|
239
|
+
const SEED_CITIZEN_MOBILE = "01012345678";
|
|
240
|
+
const SEED_CITIZEN_NAME = "Robot";
|
|
241
|
+
const CHANNEL_CODE = "samchon";
|
|
242
|
+
|
|
243
|
+
export const connection: IConnection = {
|
|
244
|
+
host:
|
|
245
|
+
typeof window !== "undefined"
|
|
246
|
+
? \`\${window.location.origin}/_backend\`
|
|
247
|
+
: (process?.env?.NEXT_PUBLIC_API_HOST ?? ${JSON.stringify(backend.host)}),
|
|
248
|
+
headers: {
|
|
249
|
+
Authorization: process?.env?.NEXT_PUBLIC_API_TOKEN ?? "",
|
|
250
|
+
},
|
|
251
|
+
simulate:
|
|
252
|
+
typeof process !== "undefined" &&
|
|
253
|
+
process.env.NEXT_PUBLIC_API_SIMULATE === "true",
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Choose which actor's session the current page needs based on the URL.
|
|
258
|
+
* Defaults to plain customer (read-only catalog browsing). Admin and seller
|
|
259
|
+
* surfaces sign in with the seed account; coupons / orders / wallet /
|
|
260
|
+
* checkout / account need a citizen (the customer plus mobile/name).
|
|
261
|
+
*/
|
|
262
|
+
function scopeForPathname(pathname: string | null | undefined): Exclude<AuthScope, null> {
|
|
263
|
+
if (typeof pathname !== "string") return "customer";
|
|
264
|
+
if (pathname.startsWith("/admin")) return "administrator";
|
|
265
|
+
if (pathname.startsWith("/seller")) return "seller";
|
|
266
|
+
if (
|
|
267
|
+
pathname.startsWith("/cart") ||
|
|
268
|
+
pathname.startsWith("/orders") ||
|
|
269
|
+
pathname.startsWith("/checkout") ||
|
|
270
|
+
pathname.startsWith("/wallet") ||
|
|
271
|
+
pathname.startsWith("/coupons") ||
|
|
272
|
+
pathname.startsWith("/account")
|
|
273
|
+
) {
|
|
274
|
+
return "citizen";
|
|
275
|
+
}
|
|
276
|
+
return "customer";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function requestJson<T>(
|
|
280
|
+
method: "POST" | "PUT",
|
|
281
|
+
path: string,
|
|
282
|
+
body: unknown,
|
|
283
|
+
): Promise<T> {
|
|
284
|
+
const res = await fetch(\`/_backend\${path}\`, {
|
|
285
|
+
method,
|
|
286
|
+
headers: { "Content-Type": "application/json", ...auth() },
|
|
287
|
+
body: JSON.stringify(body),
|
|
288
|
+
});
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
const text = await res.text().catch(() => "");
|
|
291
|
+
throw new Error(\`\${method} \${path} → \${res.status}: \${text.slice(0, 200)}\`);
|
|
292
|
+
}
|
|
293
|
+
return (await res.json()) as T;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function auth(): Record<string, string> {
|
|
297
|
+
const a = connection.headers?.Authorization;
|
|
298
|
+
return typeof a === "string" && a.length > 0 ? { Authorization: a } : {};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function applyToken(payload: unknown): void {
|
|
302
|
+
const raw = payload as {
|
|
303
|
+
setHeaders?: { Authorization?: unknown } | null;
|
|
304
|
+
token?: { access?: unknown } | null;
|
|
305
|
+
} | null;
|
|
306
|
+
const fromSetHeaders =
|
|
307
|
+
typeof raw?.setHeaders?.Authorization === "string"
|
|
308
|
+
? (raw.setHeaders.Authorization as string)
|
|
309
|
+
: null;
|
|
310
|
+
const fromTokenAccess =
|
|
311
|
+
typeof raw?.token?.access === "string"
|
|
312
|
+
? \`Bearer \${raw.token.access as string}\`
|
|
313
|
+
: null;
|
|
314
|
+
const token = fromSetHeaders ?? fromTokenAccess;
|
|
315
|
+
if (token === null) {
|
|
316
|
+
throw new Error("Auth bootstrap returned 2xx but no token in the body.");
|
|
317
|
+
}
|
|
318
|
+
if (connection.headers !== undefined) {
|
|
319
|
+
connection.headers.Authorization = token;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function ensureCustomer(): Promise<void> {
|
|
324
|
+
// The customer \`POST /authenticate\` is the only call that issues a
|
|
325
|
+
// new JWT — every subsequent activate/login attaches role grants to
|
|
326
|
+
// the same customer server-side and returns the updated customer
|
|
327
|
+
// object with NO \`setHeaders\` / \`token\` field. Reusing the bearer
|
|
328
|
+
// header from this call works for citizen / member / admin / seller
|
|
329
|
+
// requests after the corresponding activate or login has succeeded.
|
|
330
|
+
await requestJson("POST", "/shoppings/customers/authenticate", {
|
|
331
|
+
channel_code: CHANNEL_CODE,
|
|
332
|
+
external_user: null,
|
|
333
|
+
href: typeof window !== "undefined" ? window.location.href : "http://localhost:3000",
|
|
334
|
+
referrer: typeof document !== "undefined" ? document.referrer || null : null,
|
|
335
|
+
}).then(applyToken);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function ensureCitizen(): Promise<void> {
|
|
339
|
+
await ensureCustomer();
|
|
340
|
+
// \`activate\` upgrades the current customer to a citizen by attaching
|
|
341
|
+
// mobile/name. Returns the updated customer (no \`setHeaders\` / no
|
|
342
|
+
// \`token\` field) so the customer JWT from \`ensureCustomer\` stays the
|
|
343
|
+
// auth header for citizen-scoped routes. Idempotent for the seeded
|
|
344
|
+
// account; backend rejects re-activation with 409/422 which we
|
|
345
|
+
// swallow as success.
|
|
346
|
+
try {
|
|
347
|
+
await requestJson("POST", "/shoppings/customers/authenticate/activate", {
|
|
348
|
+
mobile: SEED_CITIZEN_MOBILE,
|
|
349
|
+
name: SEED_CITIZEN_NAME,
|
|
350
|
+
});
|
|
351
|
+
} catch (e: unknown) {
|
|
352
|
+
if (e instanceof Error && /already|conflict|409|422/i.test(e.message)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
throw e;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function ensureMemberLogin(): Promise<void> {
|
|
360
|
+
await ensureCustomer();
|
|
361
|
+
// \`login\` is registered as PUT on the samchon/shopping backend
|
|
362
|
+
// (\`@TypedRoute.Put("login")\`), not POST. The response has no
|
|
363
|
+
// \`setHeaders\` / no \`token\` field — the existing customer JWT now
|
|
364
|
+
// carries member privileges server-side, so no header rewrite is
|
|
365
|
+
// needed.
|
|
366
|
+
await requestJson("PUT", "/shoppings/customers/authenticate/login", {
|
|
367
|
+
email: SEED_EMAIL,
|
|
368
|
+
password: SEED_PASSWORD,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function ensureAdministrator(): Promise<void> {
|
|
373
|
+
await ensureMemberLogin();
|
|
374
|
+
await requestJson("PUT", "/shoppings/admins/authenticate/login", {
|
|
375
|
+
email: SEED_EMAIL,
|
|
376
|
+
password: SEED_PASSWORD,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function ensureSeller(): Promise<void> {
|
|
381
|
+
await ensureMemberLogin();
|
|
382
|
+
await requestJson("PUT", "/shoppings/sellers/authenticate/login", {
|
|
383
|
+
email: SEED_EMAIL,
|
|
384
|
+
password: SEED_PASSWORD,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Bootstrap (or rotate) the connection's auth token to the scope demanded
|
|
390
|
+
* by the current pathname.
|
|
391
|
+
*
|
|
392
|
+
* Memoized per scope: the first call per scope drives the fetch chain,
|
|
393
|
+
* concurrent callers reuse the same promise, and successful runs are
|
|
394
|
+
* cached so navigations within the same scope are instant. Failures clear
|
|
395
|
+
* the cache so the next attempt retries cleanly.
|
|
396
|
+
*
|
|
397
|
+
* When \`simulate: true\` is on, this is a no-op because the SDK never
|
|
398
|
+
* touches the network in mock mode.
|
|
399
|
+
*/
|
|
400
|
+
export function bootstrapAuth(pathname?: string): Promise<void> {
|
|
401
|
+
if (connection.simulate === true) {
|
|
402
|
+
activeScope = "customer";
|
|
403
|
+
return Promise.resolve();
|
|
404
|
+
}
|
|
405
|
+
const scope = scopeForPathname(pathname);
|
|
406
|
+
if (scope === activeScope && scopePromises[scope] !== undefined) {
|
|
407
|
+
return scopePromises[scope]!;
|
|
408
|
+
}
|
|
409
|
+
const promise = (async () => {
|
|
410
|
+
if (scope === "administrator") await ensureAdministrator();
|
|
411
|
+
else if (scope === "seller") await ensureSeller();
|
|
412
|
+
else if (scope === "citizen") await ensureCitizen();
|
|
413
|
+
else await ensureCustomer();
|
|
414
|
+
})()
|
|
415
|
+
.then(() => {
|
|
416
|
+
activeScope = scope;
|
|
417
|
+
})
|
|
418
|
+
.catch((err: unknown) => {
|
|
419
|
+
scopePromises[scope] = undefined;
|
|
420
|
+
throw err;
|
|
421
|
+
});
|
|
422
|
+
scopePromises[scope] = promise;
|
|
423
|
+
return promise;
|
|
424
|
+
}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
return `import type { IConnection } from "@nestia/fetcher";
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Mock connection used by every generated page.
|
|
431
|
+
*
|
|
432
|
+
* Defaults to \`simulate: true\` so the project boots usefully without a
|
|
433
|
+
* live backend — the SDK returns typia-generated mock responses for every
|
|
434
|
+
* endpoint. Set \`NEXT_PUBLIC_API_SIMULATE=false\` (and point
|
|
435
|
+
* \`NEXT_PUBLIC_API_HOST\` at the real server) to switch to live mode.
|
|
436
|
+
*/
|
|
437
|
+
export const connection: IConnection = {
|
|
438
|
+
host:
|
|
439
|
+
typeof process !== "undefined"
|
|
440
|
+
? process.env.NEXT_PUBLIC_API_HOST ?? "http://127.0.0.1:37001"
|
|
441
|
+
: "http://127.0.0.1:37001",
|
|
442
|
+
headers: {
|
|
443
|
+
Authorization: process?.env?.NEXT_PUBLIC_API_TOKEN ?? "Bearer preview",
|
|
444
|
+
},
|
|
445
|
+
simulate:
|
|
446
|
+
typeof process === "undefined" ||
|
|
447
|
+
process.env.NEXT_PUBLIC_API_SIMULATE !== "false",
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* No-op auth bootstrap for the interface (no-backend) variant. Accepts
|
|
452
|
+
* (and ignores) the pathname so the root layout can call
|
|
453
|
+
* \`bootstrapAuth(pathname)\` unconditionally, no matter which AutoView
|
|
454
|
+
* source generated the project.
|
|
455
|
+
*/
|
|
456
|
+
export function bootstrapAuth(_pathname?: string): Promise<void> {
|
|
457
|
+
return Promise.resolve();
|
|
458
|
+
}
|
|
459
|
+
`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const BOOTSTRAP_GATE_TSX = `"use client";
|
|
463
|
+
|
|
464
|
+
import { usePathname } from "next/navigation";
|
|
465
|
+
import { useEffect } from "react";
|
|
466
|
+
|
|
467
|
+
import { bootstrapAuth } from "@/src/lib/connection";
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Best-effort auth bootstrap. Attempts to issue the backend's token for the
|
|
471
|
+
* current actor scope on navigation, but NEVER blocks rendering.
|
|
472
|
+
*
|
|
473
|
+
* Auth must not gate the whole app: public screens (catalogs, lists) work
|
|
474
|
+
* without a token, and protected screens surface their own 401 through their
|
|
475
|
+
* normal load/error state. An auth-required backend (shopping / ERP) or a
|
|
476
|
+
* backend that is simply down used to take the ENTIRE app down behind a
|
|
477
|
+
* "Backend not ready" wall — even the public screens. It no longer does:
|
|
478
|
+
* the bootstrap runs in the background and children render immediately.
|
|
479
|
+
*/
|
|
480
|
+
export default function BootstrapGate({
|
|
481
|
+
children,
|
|
482
|
+
}: {
|
|
483
|
+
children: React.ReactNode;
|
|
484
|
+
}) {
|
|
485
|
+
const pathname = usePathname();
|
|
486
|
+
|
|
487
|
+
useEffect(() => {
|
|
488
|
+
bootstrapAuth(pathname).catch(() => {
|
|
489
|
+
// Bootstrap failed (no seeded account, auth-required backend, or backend
|
|
490
|
+
// down). Render anyway — each page handles its own loading / error state.
|
|
491
|
+
});
|
|
492
|
+
}, [pathname]);
|
|
493
|
+
|
|
494
|
+
return <>{children}</>;
|
|
495
|
+
}
|
|
496
|
+
`;
|
|
497
|
+
|
|
498
|
+
const ROOT_LAYOUT_TSX = `import type { Metadata } from "next";
|
|
499
|
+
import { Archivo } from "next/font/google";
|
|
500
|
+
import "./globals.css";
|
|
501
|
+
|
|
502
|
+
import { AppShell } from "@/components/AppShell";
|
|
503
|
+
import BootstrapGate from "@/src/components/BootstrapGate";
|
|
504
|
+
import { APP_NAME } from "@/src/lib/nav";
|
|
505
|
+
|
|
506
|
+
// Industrial grotesque carrying the whole UI. Wires the \`--font-sans\` variable
|
|
507
|
+
// the theme reads; the gate override must keep it or headings fall back to the
|
|
508
|
+
// browser's default serif.
|
|
509
|
+
const archivo = Archivo({
|
|
510
|
+
subsets: ["latin"],
|
|
511
|
+
variable: "--font-sans",
|
|
512
|
+
display: "swap",
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
export const metadata: Metadata = {
|
|
516
|
+
title: APP_NAME,
|
|
517
|
+
description: "Generated by AutoView from the API's OpenAPI document.",
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
export default function RootLayout({
|
|
521
|
+
children,
|
|
522
|
+
}: {
|
|
523
|
+
children: React.ReactNode;
|
|
524
|
+
}) {
|
|
525
|
+
return (
|
|
526
|
+
<html lang="en" className={\`dark \${archivo.variable}\`} suppressHydrationWarning>
|
|
527
|
+
<body className="min-h-screen bg-background font-sans antialiased">
|
|
528
|
+
<BootstrapGate>
|
|
529
|
+
<AppShell>{children}</AppShell>
|
|
530
|
+
</BootstrapGate>
|
|
531
|
+
</body>
|
|
532
|
+
</html>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
`;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Generate `src/lib/nav.ts` — the sidebar navigation config the AppShell reads.
|
|
539
|
+
* Top-level resources (chain depth 1) become nav items, grouped into data
|
|
540
|
+
* "Resources" (list tables) and "System" (singleton reads). The app name comes
|
|
541
|
+
* from the OpenAPI document title.
|
|
542
|
+
*/
|
|
543
|
+
export function renderNavTs(
|
|
544
|
+
document: OpenApi.IDocument,
|
|
545
|
+
plan: IAutoViewProductPlan,
|
|
546
|
+
): string {
|
|
547
|
+
const appName = (document.info?.title ?? "API").trim() || "API";
|
|
548
|
+
const navScreens = plan.screens.filter(
|
|
549
|
+
(s) =>
|
|
550
|
+
(s.depth === undefined || s.depth === 1) &&
|
|
551
|
+
!s.path.includes("[") &&
|
|
552
|
+
(s.uiPattern === "table" ||
|
|
553
|
+
s.uiPattern === "catalog" ||
|
|
554
|
+
s.uiPattern === "detail"),
|
|
555
|
+
);
|
|
556
|
+
// Singleton reads go under "System"; browsable resources go under "Resources",
|
|
557
|
+
// but on a large swagger that one section is 36 flat items. Group them by the
|
|
558
|
+
// resource name's leading token so related resources collapse into a section
|
|
559
|
+
// (shield_information_barrier* → "Shield", legal_hold_* → "Legal"). A token
|
|
560
|
+
// shared by ≥2 resources becomes its own section; lone resources stay under
|
|
561
|
+
// the generic "Resources" heading. Purely cosmetic — no screen is removed.
|
|
562
|
+
const groupKey = new Map<string, string>(); // href → leading token (singularized)
|
|
563
|
+
const tokenCount = new Map<string, number>();
|
|
564
|
+
for (const s of navScreens) {
|
|
565
|
+
if (s.uiPattern === "detail") continue;
|
|
566
|
+
const resource = s.path.replace(/^\//, "").split("/")[0] ?? "";
|
|
567
|
+
const token = singularizeToken(resource.split("_")[0] ?? resource);
|
|
568
|
+
groupKey.set(s.path, token);
|
|
569
|
+
tokenCount.set(token, (tokenCount.get(token) ?? 0) + 1);
|
|
570
|
+
}
|
|
571
|
+
const items = navScreens.map((s) => {
|
|
572
|
+
if (s.uiPattern === "detail") {
|
|
573
|
+
return { href: s.path, title: s.title, group: "System" };
|
|
574
|
+
}
|
|
575
|
+
const token = groupKey.get(s.path) ?? "";
|
|
576
|
+
const group =
|
|
577
|
+
(tokenCount.get(token) ?? 0) >= 2 ? titleCaseToken(token) : "Resources";
|
|
578
|
+
return { href: s.path, title: s.title, group };
|
|
579
|
+
});
|
|
580
|
+
// "Resources" first, named prefix groups alphabetically, "System" last.
|
|
581
|
+
const rank = (g: string): number =>
|
|
582
|
+
g === "Resources" ? 0 : g === "System" ? 2 : 1;
|
|
583
|
+
items.sort(
|
|
584
|
+
(a, b) =>
|
|
585
|
+
rank(a.group) - rank(b.group) ||
|
|
586
|
+
a.group.localeCompare(b.group) ||
|
|
587
|
+
a.title.localeCompare(b.title),
|
|
588
|
+
);
|
|
589
|
+
return [
|
|
590
|
+
`// Generated by AutoView — sidebar navigation derived from the top-level`,
|
|
591
|
+
`// resources of the OpenAPI document.`,
|
|
592
|
+
`export const APP_NAME = ${JSON.stringify(appName)};`,
|
|
593
|
+
``,
|
|
594
|
+
`export interface NavItem {`,
|
|
595
|
+
` href: string;`,
|
|
596
|
+
` title: string;`,
|
|
597
|
+
` group: string;`,
|
|
598
|
+
`}`,
|
|
599
|
+
``,
|
|
600
|
+
`export const NAV: NavItem[] = ${JSON.stringify(items, null, 2)};`,
|
|
601
|
+
``,
|
|
602
|
+
].join("\n");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Crude singularization of a leading nav token (`folders` → `folder`), used
|
|
606
|
+
* only to bucket related resources; `status`-style 2-letter stems are left
|
|
607
|
+
* alone to avoid over-trimming. */
|
|
608
|
+
function singularizeToken(token: string): string {
|
|
609
|
+
return token.length > 3 && token.endsWith("s") ? token.slice(0, -1) : token;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Title-case a single token for a nav section heading (`shield` → `Shield`). */
|
|
613
|
+
function titleCaseToken(token: string): string {
|
|
614
|
+
return token.length === 0
|
|
615
|
+
? token
|
|
616
|
+
: token.charAt(0).toUpperCase() + token.slice(1);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const SDK_ADAPTER_TS = `// AutoView SDK adapter. Pages import \`api\` from this module instead of
|
|
620
|
+
// touching \`@/src/api/\` directly so the SDK surface can be replaced
|
|
621
|
+
// without rewriting every page.
|
|
622
|
+
|
|
623
|
+
import api from "@/src/api";
|
|
624
|
+
|
|
625
|
+
export default api;
|
|
626
|
+
export * from "@/src/api";
|
|
627
|
+
`;
|