@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,104 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { resourcePlan } from "./resourcePlan";
|
|
4
|
+
import { IAutoViewEndpoint } from "./toEndpoints";
|
|
5
|
+
|
|
6
|
+
const ep = (over: Partial<IAutoViewEndpoint>): IAutoViewEndpoint => ({
|
|
7
|
+
method: "get",
|
|
8
|
+
path: "/items",
|
|
9
|
+
accessor: ["items", "index"],
|
|
10
|
+
name: "index",
|
|
11
|
+
description: "",
|
|
12
|
+
parameters: [],
|
|
13
|
+
query: null,
|
|
14
|
+
requestBody: null,
|
|
15
|
+
responseBody: null,
|
|
16
|
+
...over,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("resourcePlan — deterministic IA", () => {
|
|
20
|
+
it("a full-CRUD resource yields list + detail + create + edit", () => {
|
|
21
|
+
const screens = resourcePlan([
|
|
22
|
+
ep({ method: "get", path: "/pets", accessor: ["pets", "index"] }),
|
|
23
|
+
ep({
|
|
24
|
+
method: "get",
|
|
25
|
+
path: "/pets/{petId}",
|
|
26
|
+
accessor: ["pets", "at"],
|
|
27
|
+
parameters: [{ name: "petId", description: "", schema: { type: "string" } as never }],
|
|
28
|
+
}),
|
|
29
|
+
ep({ method: "post", path: "/pets", accessor: ["pets", "create"] }),
|
|
30
|
+
ep({
|
|
31
|
+
method: "put",
|
|
32
|
+
path: "/pets/{petId}",
|
|
33
|
+
accessor: ["pets", "update"],
|
|
34
|
+
parameters: [{ name: "petId", description: "", schema: { type: "string" } as never }],
|
|
35
|
+
}),
|
|
36
|
+
ep({
|
|
37
|
+
method: "delete",
|
|
38
|
+
path: "/pets/{petId}",
|
|
39
|
+
accessor: ["pets", "erase"],
|
|
40
|
+
parameters: [{ name: "petId", description: "", schema: { type: "string" } as never }],
|
|
41
|
+
}),
|
|
42
|
+
]);
|
|
43
|
+
expect(screens.map((s) => s.path)).toEqual([
|
|
44
|
+
"/pets",
|
|
45
|
+
"/pets/[petsId]",
|
|
46
|
+
"/pets/new",
|
|
47
|
+
"/pets/[petsId]/edit",
|
|
48
|
+
]);
|
|
49
|
+
expect(screens.map((s) => s.kind)).toEqual(["list", "detail", "create", "edit"]);
|
|
50
|
+
// detail screen hosts update + delete as secondary actions
|
|
51
|
+
const detail = screens.find((s) => s.kind === "detail")!;
|
|
52
|
+
expect(detail.secondary.some((c) => c.role === "update")).toBe(true);
|
|
53
|
+
expect(detail.secondary.some((c) => c.role === "delete")).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("a read-only resource yields only a list screen", () => {
|
|
57
|
+
const screens = resourcePlan([
|
|
58
|
+
ep({ method: "get", path: "/inventory", accessor: ["inventory", "index"] }),
|
|
59
|
+
]);
|
|
60
|
+
expect(screens.map((s) => s.kind)).toEqual(["list"]);
|
|
61
|
+
expect(screens[0]!.path).toBe("/inventory");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("uses a canonical per-resource param name in dynamic routes", () => {
|
|
65
|
+
const screens = resourcePlan([
|
|
66
|
+
ep({
|
|
67
|
+
method: "get",
|
|
68
|
+
path: "/users/{username}",
|
|
69
|
+
accessor: ["users", "at"],
|
|
70
|
+
parameters: [{ name: "username", description: "", schema: { type: "string" } as never }],
|
|
71
|
+
}),
|
|
72
|
+
]);
|
|
73
|
+
expect(screens[0]!.path).toBe("/users/[usersId]");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("nests a sub-resource under its parent, mirroring the swagger path", () => {
|
|
77
|
+
const param = (name: string) => ({
|
|
78
|
+
name,
|
|
79
|
+
description: "",
|
|
80
|
+
schema: { type: "string" } as never,
|
|
81
|
+
});
|
|
82
|
+
const screens = resourcePlan([
|
|
83
|
+
// top-level sales
|
|
84
|
+
ep({ method: "get", path: "/shoppings/admins/sales", accessor: ["shoppings", "admins", "sales", "index"] }),
|
|
85
|
+
ep({ method: "get", path: "/shoppings/admins/sales/{saleId}", accessor: ["shoppings", "admins", "sales", "at"], parameters: [param("saleId")] }),
|
|
86
|
+
// nested questions under a sale
|
|
87
|
+
ep({ method: "get", path: "/shoppings/admins/sales/{saleId}/questions", accessor: ["shoppings", "admins", "sales", "questions", "index"], parameters: [param("saleId")] }),
|
|
88
|
+
ep({ method: "get", path: "/shoppings/admins/sales/{saleId}/questions/{id}", accessor: ["shoppings", "admins", "sales", "questions", "at"], parameters: [param("saleId"), param("id")] }),
|
|
89
|
+
]);
|
|
90
|
+
const paths = screens.map((s) => s.path);
|
|
91
|
+
// namespace/actor prefix stripped; sales is top-level (depth 1)
|
|
92
|
+
expect(paths).toContain("/sales");
|
|
93
|
+
expect(paths).toContain("/sales/[salesId]");
|
|
94
|
+
// questions nests under the sale, carrying the parent param in the route
|
|
95
|
+
expect(paths).toContain("/sales/[salesId]/questions");
|
|
96
|
+
expect(paths).toContain("/sales/[salesId]/questions/[questionsId]");
|
|
97
|
+
const questionsList = screens.find((s) => s.path === "/sales/[salesId]/questions")!;
|
|
98
|
+
expect(questionsList.depth).toBe(2);
|
|
99
|
+
expect(questionsList.parentPath).toBe("/sales/[salesId]");
|
|
100
|
+
const salesList = screens.find((s) => s.path === "/sales")!;
|
|
101
|
+
expect(salesList.depth).toBe(1);
|
|
102
|
+
expect(salesList.parentPath).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
classifyEndpoints,
|
|
5
|
+
EndpointRole,
|
|
6
|
+
IClassifiedEndpoint,
|
|
7
|
+
IResourceGroup,
|
|
8
|
+
} from "./classifyEndpoints";
|
|
9
|
+
import { IAutoViewEndpoint } from "./toEndpoints";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic information architecture — the second brick of the "정확한 틀".
|
|
13
|
+
*
|
|
14
|
+
* Turns the classified resource groups into a concrete, reproducible screen
|
|
15
|
+
* set: for each resource, exactly the screens its CRUD roles justify, with real
|
|
16
|
+
* Next.js paths and the endpoints each screen composes. No LLM, no commerce
|
|
17
|
+
* baseline, no "5-25 screens, compose, don't enumerate" guesswork — every
|
|
18
|
+
* resource that exposes a capability gets the corresponding screen, and
|
|
19
|
+
* nothing more.
|
|
20
|
+
*
|
|
21
|
+
* This is what replaces the LLM ProductPlan's IShop-flavored improvisation:
|
|
22
|
+
* the Render phase later fills these slots with TSX, but the SET of screens is
|
|
23
|
+
* decided here, structurally.
|
|
24
|
+
*/
|
|
25
|
+
export type ScreenKind = "list" | "detail" | "create" | "edit";
|
|
26
|
+
|
|
27
|
+
export interface IPlannedScreen {
|
|
28
|
+
/** Next.js App Router path, dynamic segments in `[brackets]`. */
|
|
29
|
+
path: string;
|
|
30
|
+
resource: string;
|
|
31
|
+
kind: ScreenKind;
|
|
32
|
+
/** Hierarchy depth: 1 = top-level resource, ≥2 = nested under a parent. */
|
|
33
|
+
depth: number;
|
|
34
|
+
/** Parent collection path (`/sales/[saleId]`) for a nested resource, else null. */
|
|
35
|
+
parentPath: string | null;
|
|
36
|
+
title: string;
|
|
37
|
+
/** Primary data-source / target endpoint for the screen. */
|
|
38
|
+
primary: IAutoViewEndpoint;
|
|
39
|
+
/**
|
|
40
|
+
* Secondary endpoints the screen wires in — e.g. a detail screen surfaces
|
|
41
|
+
* its update / delete / action endpoints as buttons; a list screen links to
|
|
42
|
+
* detail. Empty for create.
|
|
43
|
+
*/
|
|
44
|
+
secondary: IClassifiedEndpoint[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pick(group: IResourceGroup, role: EndpointRole): IClassifiedEndpoint[] {
|
|
48
|
+
return group.endpoints.filter((c) => c.role === role);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function titleize(resource: string): string {
|
|
52
|
+
const spaced = resource.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
53
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Canonical route param name for a resource: `<name>Id`. Derived from the
|
|
58
|
+
* resource NAME (not the endpoint's param), so it is identical everywhere the
|
|
59
|
+
* resource appears (its own detail and as a parent of a sub-resource) and
|
|
60
|
+
* distinct from every other resource's param. This guarantees a route never has
|
|
61
|
+
* a sibling-slug conflict (`/sales/[id]` vs `/sales/[saleId]`) nor a repeated
|
|
62
|
+
* slug within one path (`/sales/[id]/questions/[id]`) — both of which Next.js
|
|
63
|
+
* rejects at boot. The SDK's own param names are recovered separately by
|
|
64
|
+
* position when the page issues its fetch.
|
|
65
|
+
*/
|
|
66
|
+
function canonParam(name: string): string {
|
|
67
|
+
return `${name.replace(/[^A-Za-z0-9]/g, "")}Id`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The collection route for a resource group, mirroring the swagger hierarchy:
|
|
72
|
+
* every parent link contributes `<name>/[<canonParam>]`, the leaf its bare name.
|
|
73
|
+
* `[sales, questions]` → `/sales/[salesId]/questions`. `[sales]` → `/sales`.
|
|
74
|
+
*/
|
|
75
|
+
/** A parent link's route part: `sales/[salesId]` when item-addressed, else just
|
|
76
|
+
* `coupons` (a path prefix with no `{param}`, so no bracket). */
|
|
77
|
+
function parentPart(link: { name: string; param: string | null }): string {
|
|
78
|
+
return link.param !== null ? `${link.name}/[${canonParam(link.name)}]` : link.name;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collectionPath(group: IResourceGroup): string {
|
|
82
|
+
const links = group.chain;
|
|
83
|
+
const parts = links.map((c, i) => (i < links.length - 1 ? parentPart(c) : c.name));
|
|
84
|
+
return `/${parts.join("/")}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The parent collection path (`/sales/[salesId]`) for a nested group, else null. */
|
|
88
|
+
function parentPathOf(group: IResourceGroup): string | null {
|
|
89
|
+
if (group.chain.length < 2) return null;
|
|
90
|
+
const parents = group.chain.slice(0, -1);
|
|
91
|
+
return `/${parents.map(parentPart).join("/")}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Item route param for a group's own entity (`/sales` → `[salesId]`). */
|
|
95
|
+
function itemParam(group: IResourceGroup): string {
|
|
96
|
+
return canonParam(group.resource);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function planResource(group: IResourceGroup): IPlannedScreen[] {
|
|
100
|
+
const screens: IPlannedScreen[] = [];
|
|
101
|
+
const resource = group.resource;
|
|
102
|
+
const label = titleize(resource);
|
|
103
|
+
const depth = group.chain.length;
|
|
104
|
+
const parentPath = parentPathOf(group);
|
|
105
|
+
const base = collectionPath(group);
|
|
106
|
+
const common = { resource, depth, parentPath };
|
|
107
|
+
|
|
108
|
+
// Prefer the top-level enumeration as the resource's primary list: the real
|
|
109
|
+
// `/v2/droplets` (no path params) over an id-scoped sub-view that happens to
|
|
110
|
+
// classify as a list (`/v2/droplets/{id}/destroy_with_associated_resources`),
|
|
111
|
+
// whose response is NOT the resource's own rows. Fewest path params wins;
|
|
112
|
+
// stable otherwise (list before search at equal depth).
|
|
113
|
+
const listish = [...pick(group, "list"), ...pick(group, "search")].sort(
|
|
114
|
+
(a, b) => a.endpoint.parameters.length - b.endpoint.parameters.length,
|
|
115
|
+
);
|
|
116
|
+
const details = pick(group, "detail");
|
|
117
|
+
const creates = pick(group, "create");
|
|
118
|
+
const updates = pick(group, "update");
|
|
119
|
+
const deletes = pick(group, "delete");
|
|
120
|
+
const actions = pick(group, "action");
|
|
121
|
+
|
|
122
|
+
// List / search screen — the resource's home.
|
|
123
|
+
if (listish.length > 0) {
|
|
124
|
+
screens.push({
|
|
125
|
+
...common,
|
|
126
|
+
path: base,
|
|
127
|
+
kind: "list",
|
|
128
|
+
title: label,
|
|
129
|
+
primary: listish[0]!.endpoint,
|
|
130
|
+
// extra search variants + the row-action targets (detail/delete) the
|
|
131
|
+
// list links to.
|
|
132
|
+
secondary: [...listish.slice(1), ...details, ...deletes],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Detail screen — one entity. Hosts update/delete/action as buttons.
|
|
137
|
+
if (details.length > 0) {
|
|
138
|
+
screens.push({
|
|
139
|
+
...common,
|
|
140
|
+
path: `${base}/[${itemParam(group)}]`,
|
|
141
|
+
kind: "detail",
|
|
142
|
+
title: `${label} detail`,
|
|
143
|
+
primary: details[0]!.endpoint,
|
|
144
|
+
secondary: [...updates, ...deletes, ...actions],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Create screen — a form from the POST requestBody schema.
|
|
149
|
+
if (creates.length > 0) {
|
|
150
|
+
screens.push({
|
|
151
|
+
...common,
|
|
152
|
+
path: `${base}/new`,
|
|
153
|
+
kind: "create",
|
|
154
|
+
title: `New ${label.toLowerCase()}`,
|
|
155
|
+
primary: creates[0]!.endpoint,
|
|
156
|
+
secondary: [],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Edit screen — a form from the PUT/PATCH requestBody, prefilled by detail.
|
|
161
|
+
if (updates.length > 0) {
|
|
162
|
+
screens.push({
|
|
163
|
+
...common,
|
|
164
|
+
path: `${base}/[${itemParam(group)}]/edit`,
|
|
165
|
+
kind: "edit",
|
|
166
|
+
title: `Edit ${label.toLowerCase()}`,
|
|
167
|
+
primary: updates[0]!.endpoint,
|
|
168
|
+
secondary: details,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return screens;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function resourcePlan(
|
|
176
|
+
endpoints: IAutoViewEndpoint[],
|
|
177
|
+
document?: OpenApi.IDocument,
|
|
178
|
+
): IPlannedScreen[] {
|
|
179
|
+
return classifyEndpoints(endpoints, document).flatMap(planResource);
|
|
180
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { OpenApiConverter } from "@typia/utils";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { sliceDocument } from "./sliceDocument";
|
|
5
|
+
import { toEndpoints } from "./toEndpoints";
|
|
6
|
+
|
|
7
|
+
function doc(
|
|
8
|
+
paths: Record<string, unknown>,
|
|
9
|
+
schemas: Record<string, unknown> = {},
|
|
10
|
+
) {
|
|
11
|
+
return OpenApiConverter.upgradeDocument({
|
|
12
|
+
openapi: "3.0.0",
|
|
13
|
+
info: { title: "t", version: "1.0.0" },
|
|
14
|
+
paths,
|
|
15
|
+
components: { schemas },
|
|
16
|
+
// biome-ignore lint: test fixture
|
|
17
|
+
} as never);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const objResp = (name: string) => ({
|
|
21
|
+
200: { description: "ok", content: { "application/json": { schema: { $ref: `#/components/schemas/${name}` } } } },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const fixture = doc(
|
|
25
|
+
{
|
|
26
|
+
"/sales": { get: { operationId: "listSales", responses: objResp("Sale") } },
|
|
27
|
+
"/orders": { get: { operationId: "listOrders", responses: objResp("Order") } },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
// Sale references Seller (transitively kept); Order references nothing extra.
|
|
31
|
+
Sale: { type: "object", properties: { id: { type: "string" }, seller: { $ref: "#/components/schemas/Seller" } } },
|
|
32
|
+
Seller: { type: "object", properties: { id: { type: "string" }, name: { type: "string" } } },
|
|
33
|
+
Order: { type: "object", properties: { id: { type: "string" } } },
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
describe("sliceDocument", () => {
|
|
38
|
+
it("keeps only the included paths", () => {
|
|
39
|
+
const sliced = sliceDocument(fixture, { include: ["/sales"] });
|
|
40
|
+
expect(Object.keys(sliced.paths)).toEqual(["/sales"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("prunes component schemas to the transitive closure of kept operations", () => {
|
|
44
|
+
const sliced = sliceDocument(fixture, { include: ["/sales"] });
|
|
45
|
+
const kept = Object.keys(sliced.components?.schemas ?? {}).sort();
|
|
46
|
+
// Sale (response) + Seller (referenced by Sale) kept; Order dropped.
|
|
47
|
+
expect(kept).toEqual(["Sale", "Seller"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("drops a path's unincluded sibling methods but keeps included ones", () => {
|
|
51
|
+
const d = doc(
|
|
52
|
+
{
|
|
53
|
+
"/items": {
|
|
54
|
+
get: { operationId: "list", responses: objResp("Item") },
|
|
55
|
+
},
|
|
56
|
+
"/items/{id}": {
|
|
57
|
+
get: { operationId: "at", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: objResp("Item") },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{ Item: { type: "object", properties: { id: { type: "string" } } } },
|
|
61
|
+
);
|
|
62
|
+
const sliced = sliceDocument(d, { exclude: ["/items/**"] });
|
|
63
|
+
expect(Object.keys(sliced.paths)).toEqual(["/items"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("is a no-op when no filter is given (same endpoints)", () => {
|
|
67
|
+
const sliced = sliceDocument(fixture, {});
|
|
68
|
+
expect(toEndpoints(sliced).map((e) => e.path).sort()).toEqual(
|
|
69
|
+
toEndpoints(fixture).map((e) => e.path).sort(),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("does not mutate the input document", () => {
|
|
74
|
+
const before = Object.keys(fixture.components?.schemas ?? {}).length;
|
|
75
|
+
sliceDocument(fixture, { include: ["/sales"] });
|
|
76
|
+
expect(Object.keys(fixture.components?.schemas ?? {}).length).toBe(before);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("leaves the kept operations renderable end to end", () => {
|
|
80
|
+
const sliced = sliceDocument(fixture, { include: ["/sales"] });
|
|
81
|
+
const eps = toEndpoints(sliced);
|
|
82
|
+
expect(eps).toHaveLength(1);
|
|
83
|
+
expect(eps[0]!.responseBody?.typeName).toBe("Sale");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
|
|
3
|
+
import { IEndpointFilter, filterEndpoints } from "./endpointFilter";
|
|
4
|
+
import { toEndpoints } from "./toEndpoints";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Slice an OpenAPI document down to an operator-selected surface — the fix for
|
|
8
|
+
* "`--include` only trims the endpoint list, but the SDK Study still compiles
|
|
9
|
+
* the WHOLE document and overflows the model's context on a large swagger".
|
|
10
|
+
*
|
|
11
|
+
* {@link filterEndpoints} answers *which screens*; this answers *which document*.
|
|
12
|
+
* It keeps only the filtered paths/methods AND prunes `components.schemas` to
|
|
13
|
+
* the transitive closure those operations actually reference, so every
|
|
14
|
+
* downstream phase (SDK Study, Product Plan, Render) sees a document whose size
|
|
15
|
+
* matches the requested slice — not the original 300-type spec.
|
|
16
|
+
*
|
|
17
|
+
* Pure: returns a new document; the input is never mutated. A no-op filter
|
|
18
|
+
* returns an equivalent document (schemas reachable from all operations).
|
|
19
|
+
*/
|
|
20
|
+
export function sliceDocument(
|
|
21
|
+
document: OpenApi.IDocument,
|
|
22
|
+
filter: IEndpointFilter = {},
|
|
23
|
+
): OpenApi.IDocument {
|
|
24
|
+
const kept = new Set(
|
|
25
|
+
filterEndpoints(toEndpoints(document), filter).map(
|
|
26
|
+
(e) => `${e.method.toLowerCase()} ${e.path}`,
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Rebuild paths, keeping only the (method, path) pairs that survived the
|
|
31
|
+
// filter. A path with no surviving method is dropped entirely.
|
|
32
|
+
const paths: Record<string, OpenApi.IPath> = {};
|
|
33
|
+
for (const [path, item] of Object.entries(document.paths ?? {})) {
|
|
34
|
+
if (item === undefined) continue;
|
|
35
|
+
const keptItem: Record<string, unknown> = {};
|
|
36
|
+
for (const [key, value] of Object.entries(item)) {
|
|
37
|
+
// Non-method members of a path item (parameters, $ref, summary…) are
|
|
38
|
+
// carried over only when at least one method on the path survives.
|
|
39
|
+
if (!HTTP_METHODS.has(key.toLowerCase())) continue;
|
|
40
|
+
if (kept.has(`${key.toLowerCase()} ${path}`)) keptItem[key] = value;
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(keptItem).length === 0) continue;
|
|
43
|
+
for (const [key, value] of Object.entries(item)) {
|
|
44
|
+
if (!HTTP_METHODS.has(key.toLowerCase())) keptItem[key] = value;
|
|
45
|
+
}
|
|
46
|
+
paths[path] = keptItem as OpenApi.IPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Compute the transitive schema closure reachable from the kept operations.
|
|
50
|
+
const allSchemas = document.components?.schemas ?? {};
|
|
51
|
+
const reachable = new Set<string>();
|
|
52
|
+
const frontier: string[] = [];
|
|
53
|
+
const seed = (schema: unknown): void => {
|
|
54
|
+
for (const name of collectRefs(schema)) {
|
|
55
|
+
if (!reachable.has(name)) {
|
|
56
|
+
reachable.add(name);
|
|
57
|
+
frontier.push(name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
for (const item of Object.values(paths)) {
|
|
62
|
+
for (const [key, op] of Object.entries(item ?? {})) {
|
|
63
|
+
if (!HTTP_METHODS.has(key.toLowerCase())) continue;
|
|
64
|
+
seed(op);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
while (frontier.length > 0) {
|
|
68
|
+
const name = frontier.pop()!;
|
|
69
|
+
seed(allSchemas[name]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const schemas: Record<string, OpenApi.IJsonSchema> = {};
|
|
73
|
+
for (const name of reachable) {
|
|
74
|
+
const schema = allSchemas[name];
|
|
75
|
+
if (schema !== undefined) schemas[name] = schema;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...document,
|
|
80
|
+
paths,
|
|
81
|
+
components: { ...document.components, schemas },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const HTTP_METHODS = new Set([
|
|
86
|
+
"get",
|
|
87
|
+
"post",
|
|
88
|
+
"put",
|
|
89
|
+
"patch",
|
|
90
|
+
"delete",
|
|
91
|
+
"head",
|
|
92
|
+
"options",
|
|
93
|
+
"trace",
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Every component name a JSON value references via `$ref`, found by walking the
|
|
98
|
+
* whole subtree (properties, items, allOf/oneOf/anyOf, additionalProperties,
|
|
99
|
+
* and arbitrarily nested objects/arrays). Returns bare names (`Sale`), not
|
|
100
|
+
* pointers.
|
|
101
|
+
*/
|
|
102
|
+
function collectRefs(value: unknown): string[] {
|
|
103
|
+
const out: string[] = [];
|
|
104
|
+
const walk = (node: unknown): void => {
|
|
105
|
+
if (node === null || typeof node !== "object") return;
|
|
106
|
+
if (Array.isArray(node)) {
|
|
107
|
+
for (const child of node) walk(child);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const obj = node as Record<string, unknown>;
|
|
111
|
+
const ref = obj.$ref;
|
|
112
|
+
if (typeof ref === "string" && ref.startsWith("#/components/schemas/")) {
|
|
113
|
+
out.push(ref.slice("#/components/schemas/".length));
|
|
114
|
+
}
|
|
115
|
+
for (const child of Object.values(obj)) walk(child);
|
|
116
|
+
};
|
|
117
|
+
walk(value);
|
|
118
|
+
return out;
|
|
119
|
+
}
|