@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,32 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { hashFrontendDependencies } from "./validateFrontendTypecheck";
|
|
4
|
+
|
|
5
|
+
describe("hashFrontendDependencies", () => {
|
|
6
|
+
it("returns a stable 16-char hex for the same package.json body", () => {
|
|
7
|
+
const a = hashFrontendDependencies({
|
|
8
|
+
"package.json": '{"name":"x","dependencies":{"next":"14"}}',
|
|
9
|
+
});
|
|
10
|
+
const b = hashFrontendDependencies({
|
|
11
|
+
"package.json": '{"name":"x","dependencies":{"next":"14"}}',
|
|
12
|
+
});
|
|
13
|
+
expect(a).toBe(b);
|
|
14
|
+
expect(a).toMatch(/^[0-9a-f]{16}$/);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("changes when package.json changes", () => {
|
|
18
|
+
expect(
|
|
19
|
+
hashFrontendDependencies({
|
|
20
|
+
"package.json": '{"name":"x","dependencies":{"next":"14"}}',
|
|
21
|
+
}),
|
|
22
|
+
).not.toBe(
|
|
23
|
+
hashFrontendDependencies({
|
|
24
|
+
"package.json": '{"name":"x","dependencies":{"next":"15"}}',
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("falls back to a fixed sentinel when package.json is missing", () => {
|
|
30
|
+
expect(hashFrontendDependencies({})).toBe("no-package-json");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ensureCachedNodeModules,
|
|
9
|
+
tryLinkNodeModules,
|
|
10
|
+
} from "./cacheNodeModules";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Per-file diagnostic emitted by {@link validateFrontendTypecheck}. Mirrors the
|
|
14
|
+
* one-line shape `tsc --noEmit` prints to stderr (`<file>(<line>,<col>): error
|
|
15
|
+
* TS<code>: <message>`) so the orchestrator can hand it back to the Render
|
|
16
|
+
* phase verbatim when scheduling a retry.
|
|
17
|
+
*/
|
|
18
|
+
export interface ITypecheckDiagnostic {
|
|
19
|
+
/** Path of the broken file, relative to the project root. */
|
|
20
|
+
file: string;
|
|
21
|
+
/** 1-indexed line where tsc anchored the error. */
|
|
22
|
+
line: number;
|
|
23
|
+
/** 1-indexed column where tsc anchored the error. */
|
|
24
|
+
column: number;
|
|
25
|
+
/** TS<n> code (e.g. `TS2339`). */
|
|
26
|
+
code: string;
|
|
27
|
+
/** Human-readable diagnostic body, one line. */
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Result of running the typecheck against a virtual frontend project. */
|
|
32
|
+
export interface IValidateFrontendTypecheckResult {
|
|
33
|
+
/** Map of broken file → diagnostics on that file. */
|
|
34
|
+
diagnostics: Map<string, ITypecheckDiagnostic[]>;
|
|
35
|
+
/** Total number of diagnostics across all files. */
|
|
36
|
+
totalErrors: number;
|
|
37
|
+
/**
|
|
38
|
+
* Markdown report suitable for dropping into `wiki/typecheck.md`. Always
|
|
39
|
+
* present even when there are zero errors — the empty body still tells the
|
|
40
|
+
* operator the gate ran.
|
|
41
|
+
*/
|
|
42
|
+
markdown: string;
|
|
43
|
+
/**
|
|
44
|
+
* Wall-clock duration of the whole check (npm install + tsc + parse) in
|
|
45
|
+
* milliseconds. Useful for the orchestrator to surface progress info without
|
|
46
|
+
* timing the call itself.
|
|
47
|
+
*/
|
|
48
|
+
elapsedMs: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Run `npm install` + `tsc --noEmit` against the assembled frontend file map
|
|
53
|
+
* and return the per-file diagnostics. The whole project is materialized into a
|
|
54
|
+
* temp directory, dependencies are installed via `npm install --silent`, and
|
|
55
|
+
* the project's own `tsconfig.json` drives the compile — same code path the
|
|
56
|
+
* external user gets when they run `npm run typecheck` themselves.
|
|
57
|
+
*
|
|
58
|
+
* The temp directory is removed on success. On failure (npm install crash,
|
|
59
|
+
* spawn ENOENT, etc.) it is left behind so the operator can inspect what the
|
|
60
|
+
* agent shipped; the path is included in the thrown error message.
|
|
61
|
+
*
|
|
62
|
+
* Returns a structured result rather than throwing on typecheck failures — type
|
|
63
|
+
* errors are the entire point of running this gate. Only infrastructural
|
|
64
|
+
* problems (npm missing, install failure) throw.
|
|
65
|
+
*/
|
|
66
|
+
export async function validateFrontendTypecheck(
|
|
67
|
+
files: Record<string, string>,
|
|
68
|
+
): Promise<IValidateFrontendTypecheckResult> {
|
|
69
|
+
const session = await FrontendTypecheckSession.open(files);
|
|
70
|
+
try {
|
|
71
|
+
return await session.typecheck();
|
|
72
|
+
} finally {
|
|
73
|
+
await session.dispose();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Reusable typecheck session. Materializes the project into a temp directory
|
|
79
|
+
* and installs dependencies once, then lets the caller mutate individual files
|
|
80
|
+
* and re-run `tsc --noEmit` repeatedly without paying the npm install cost
|
|
81
|
+
* again. Used by the typecheck-driven render retry loop, where the orchestrator
|
|
82
|
+
* needs to type-check the project, hand the diagnostics to the LLM, write the
|
|
83
|
+
* model's corrected TSX back, and type-check again — possibly several times.
|
|
84
|
+
*
|
|
85
|
+
* Always call {@link dispose} when finished so the temp directory is cleaned up.
|
|
86
|
+
* The session is single-use per process; create a new one for a new file map
|
|
87
|
+
* whose `package.json` differs.
|
|
88
|
+
*/
|
|
89
|
+
export class FrontendTypecheckSession {
|
|
90
|
+
private constructor(private readonly tempDir: string) {}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Materialize the file map into a fresh temp directory and run `npm install`
|
|
94
|
+
* once — except `node_modules` itself is symlinked from a per-package.json
|
|
95
|
+
* cache directory (`~/.cache/autoview-typecheck/<hash>/node_modules`), so the
|
|
96
|
+
* install only runs the first time the agent sees a given `package.json`
|
|
97
|
+
* body. Subsequent opens with the same dependencies finish in milliseconds
|
|
98
|
+
* instead of the ~30s npm install pays.
|
|
99
|
+
*
|
|
100
|
+
* Throws on infra failure (npm missing, install crash) and leaves the temp
|
|
101
|
+
* dir behind for the operator to inspect. Falls back to a plain in-place
|
|
102
|
+
* install when the symlink cannot be created (Windows without admin /
|
|
103
|
+
* filesystem refusing symlinks), so the behavior degrades gracefully.
|
|
104
|
+
*/
|
|
105
|
+
public static async open(
|
|
106
|
+
files: Record<string, string>,
|
|
107
|
+
): Promise<FrontendTypecheckSession> {
|
|
108
|
+
const tempDir = await fs.mkdtemp(
|
|
109
|
+
path.join(os.tmpdir(), "autoview-typecheck-"),
|
|
110
|
+
);
|
|
111
|
+
try {
|
|
112
|
+
await writeFiles(tempDir, files);
|
|
113
|
+
const hash = hashFrontendDependencies(files);
|
|
114
|
+
const packageJson = files["package.json"];
|
|
115
|
+
const cachedNodeModules =
|
|
116
|
+
packageJson !== undefined
|
|
117
|
+
? await ensureCachedNodeModules({
|
|
118
|
+
cacheNamespace: "typecheck",
|
|
119
|
+
hash,
|
|
120
|
+
packageJson,
|
|
121
|
+
})
|
|
122
|
+
: null;
|
|
123
|
+
if (cachedNodeModules !== null) {
|
|
124
|
+
const linked = await tryLinkNodeModules(tempDir, cachedNodeModules);
|
|
125
|
+
if (!linked) {
|
|
126
|
+
await installDependencies(tempDir);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
await installDependencies(tempDir);
|
|
130
|
+
}
|
|
131
|
+
return new FrontendTypecheckSession(tempDir);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
throw enrichError(err, tempDir);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Root of the temp dir backing this session. Exposed for diagnostics only. */
|
|
138
|
+
public get root(): string {
|
|
139
|
+
return this.tempDir;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Overwrite an individual file in the session's working tree. Used by the
|
|
144
|
+
* retry loop to swap in the LLM's corrected TSX before the next typecheck
|
|
145
|
+
* run. Creates intermediate directories as needed.
|
|
146
|
+
*/
|
|
147
|
+
public async writeFile(relative: string, content: string): Promise<void> {
|
|
148
|
+
const full = path.join(this.tempDir, relative);
|
|
149
|
+
await fs.mkdir(path.dirname(full), { recursive: true });
|
|
150
|
+
await fs.writeFile(full, content, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Run `tsc --noEmit` against the current state of the working tree and return
|
|
155
|
+
* the parsed diagnostics + markdown report. Cheap relative to {@link open} (no
|
|
156
|
+
* npm install) — milliseconds of process spawn plus the actual compile time.
|
|
157
|
+
*/
|
|
158
|
+
public async typecheck(): Promise<IValidateFrontendTypecheckResult> {
|
|
159
|
+
const startedAt = Date.now();
|
|
160
|
+
const stdout = await runTypecheck(this.tempDir);
|
|
161
|
+
const diagnostics = parseDiagnostics(stdout);
|
|
162
|
+
const totalErrors = [...diagnostics.values()].reduce(
|
|
163
|
+
(acc, list) => acc + list.length,
|
|
164
|
+
0,
|
|
165
|
+
);
|
|
166
|
+
return {
|
|
167
|
+
diagnostics,
|
|
168
|
+
totalErrors,
|
|
169
|
+
markdown: renderMarkdownReport(diagnostics, totalErrors),
|
|
170
|
+
elapsedMs: Date.now() - startedAt,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Remove the temp directory. Safe to call multiple times and never throws —
|
|
176
|
+
* the caller almost always invokes this from a `finally` block where a
|
|
177
|
+
* cleanup failure must not mask the original error.
|
|
178
|
+
*/
|
|
179
|
+
public async dispose(): Promise<void> {
|
|
180
|
+
await fs.rm(this.tempDir, { recursive: true, force: true }).catch(() => {});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Stable hash of the project's `package.json` so future cache-aware variants
|
|
186
|
+
* can key against it. Exposed so callers can decide whether to short-circuit
|
|
187
|
+
* the check across runs without parsing the file themselves.
|
|
188
|
+
*/
|
|
189
|
+
export function hashFrontendDependencies(
|
|
190
|
+
files: Record<string, string>,
|
|
191
|
+
): string {
|
|
192
|
+
const pkgJson = files["package.json"];
|
|
193
|
+
if (pkgJson === undefined) return "no-package-json";
|
|
194
|
+
return createHash("sha256").update(pkgJson).digest("hex").slice(0, 16);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function writeFiles(
|
|
198
|
+
root: string,
|
|
199
|
+
files: Record<string, string>,
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
for (const [relative, content] of Object.entries(files)) {
|
|
202
|
+
const full = path.join(root, relative);
|
|
203
|
+
await fs.mkdir(path.dirname(full), { recursive: true });
|
|
204
|
+
await fs.writeFile(full, content, "utf-8");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function installDependencies(root: string): Promise<void> {
|
|
209
|
+
await spawnCommand(
|
|
210
|
+
"npm",
|
|
211
|
+
["install", "--silent", "--no-audit", "--no-fund"],
|
|
212
|
+
{
|
|
213
|
+
cwd: root,
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function runTypecheck(root: string): Promise<string> {
|
|
219
|
+
// Use the locally installed tsc (npm install puts it in
|
|
220
|
+
// node_modules/.bin) so the version matches the project's typescript
|
|
221
|
+
// dependency. Capture stdout only — tsc writes diagnostics there
|
|
222
|
+
// when not in --pretty mode.
|
|
223
|
+
return spawnCommand(
|
|
224
|
+
path.join(root, "node_modules", ".bin", "tsc"),
|
|
225
|
+
["--noEmit", "--pretty", "false"],
|
|
226
|
+
{ cwd: root, allowNonZeroExit: true },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
interface ISpawnOptions {
|
|
231
|
+
cwd: string;
|
|
232
|
+
allowNonZeroExit?: boolean;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function spawnCommand(
|
|
236
|
+
cmd: string,
|
|
237
|
+
args: string[],
|
|
238
|
+
options: ISpawnOptions,
|
|
239
|
+
): Promise<string> {
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
const child = spawn(cmd, args, { cwd: options.cwd, stdio: "pipe" });
|
|
242
|
+
let stdout = "";
|
|
243
|
+
let stderr = "";
|
|
244
|
+
child.stdout.on("data", (chunk) => {
|
|
245
|
+
stdout += chunk.toString("utf-8");
|
|
246
|
+
});
|
|
247
|
+
child.stderr.on("data", (chunk) => {
|
|
248
|
+
stderr += chunk.toString("utf-8");
|
|
249
|
+
});
|
|
250
|
+
child.on("error", (err) => {
|
|
251
|
+
reject(
|
|
252
|
+
new Error(
|
|
253
|
+
`Failed to spawn \`${cmd} ${args.join(" ")}\`: ${err.message}`,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
child.on("close", (code) => {
|
|
258
|
+
if (code === 0 || options.allowNonZeroExit === true) {
|
|
259
|
+
resolve(stdout);
|
|
260
|
+
} else {
|
|
261
|
+
reject(
|
|
262
|
+
new Error(
|
|
263
|
+
`\`${cmd} ${args.join(" ")}\` exited with code ${code}` +
|
|
264
|
+
(stderr.length > 0 ? `:\n${stderr}` : ""),
|
|
265
|
+
),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const DIAGNOSTIC_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.*)$/;
|
|
273
|
+
|
|
274
|
+
function parseDiagnostics(stdout: string): Map<string, ITypecheckDiagnostic[]> {
|
|
275
|
+
const map = new Map<string, ITypecheckDiagnostic[]>();
|
|
276
|
+
for (const raw of stdout.split(/\r?\n/)) {
|
|
277
|
+
const match = DIAGNOSTIC_LINE.exec(raw);
|
|
278
|
+
if (match === null) continue;
|
|
279
|
+
const [, file, lineRaw, columnRaw, code, message] = match;
|
|
280
|
+
const diagnostic: ITypecheckDiagnostic = {
|
|
281
|
+
file: file!,
|
|
282
|
+
line: parseInt(lineRaw!, 10),
|
|
283
|
+
column: parseInt(columnRaw!, 10),
|
|
284
|
+
code: code!,
|
|
285
|
+
message: message!,
|
|
286
|
+
};
|
|
287
|
+
const bucket = map.get(diagnostic.file);
|
|
288
|
+
if (bucket === undefined) {
|
|
289
|
+
map.set(diagnostic.file, [diagnostic]);
|
|
290
|
+
} else {
|
|
291
|
+
bucket.push(diagnostic);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return map;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function renderMarkdownReport(
|
|
298
|
+
diagnostics: Map<string, ITypecheckDiagnostic[]>,
|
|
299
|
+
totalErrors: number,
|
|
300
|
+
): string {
|
|
301
|
+
if (totalErrors === 0) {
|
|
302
|
+
return [
|
|
303
|
+
"# Typecheck report",
|
|
304
|
+
"",
|
|
305
|
+
"`tsc --noEmit` succeeded against the assembled project. No errors.",
|
|
306
|
+
"",
|
|
307
|
+
].join("\n");
|
|
308
|
+
}
|
|
309
|
+
const sortedFiles = [...diagnostics.keys()].sort();
|
|
310
|
+
const sections = sortedFiles.map((file) => {
|
|
311
|
+
const list = diagnostics.get(file) ?? [];
|
|
312
|
+
const lines = list.map(
|
|
313
|
+
(d) =>
|
|
314
|
+
`- L${d.line}:${d.column} \`${d.code}\` — ${escapeBackticks(d.message)}`,
|
|
315
|
+
);
|
|
316
|
+
return [`## \`${file}\` (${list.length})`, "", ...lines, ""].join("\n");
|
|
317
|
+
});
|
|
318
|
+
return [
|
|
319
|
+
"# Typecheck report",
|
|
320
|
+
"",
|
|
321
|
+
`\`tsc --noEmit\` reported **${totalErrors}** error(s) across **${diagnostics.size}** file(s).`,
|
|
322
|
+
"",
|
|
323
|
+
...sections,
|
|
324
|
+
].join("\n");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function escapeBackticks(input: string): string {
|
|
328
|
+
return input.replace(/`/g, "\\`");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function enrichError(err: unknown, tempDir: string): Error {
|
|
332
|
+
const base = err instanceof Error ? err : new Error(String(err));
|
|
333
|
+
base.message = `${base.message}\n(project preserved at: ${tempDir})`;
|
|
334
|
+
return base;
|
|
335
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
|
|
3
|
+
import { renderNamedSchema } from "../orchestrate/utils/renderJsonSchema";
|
|
4
|
+
import {
|
|
5
|
+
classifyEndpoints,
|
|
6
|
+
filterEndpoints,
|
|
7
|
+
IAutoViewEndpoint,
|
|
8
|
+
IClassifiedEndpoint,
|
|
9
|
+
IEndpointFilter,
|
|
10
|
+
IResourceGroup,
|
|
11
|
+
planResource,
|
|
12
|
+
toEndpoints,
|
|
13
|
+
unsupportedOperations,
|
|
14
|
+
} from "../utils";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Static `--preview` explorer — the LLM-free READ-layer view.
|
|
18
|
+
*
|
|
19
|
+
* Runs only `fromSwagger → toEndpoints` (zero LLM calls) and renders a single
|
|
20
|
+
* self-contained HTML page so the operator can confirm, in seconds, exactly how
|
|
21
|
+
* AutoView parsed their swagger: every endpoint grouped by resource, with its
|
|
22
|
+
* params / query / request / response types expanded. The point is to surface
|
|
23
|
+
* the READ layer's understanding *before* paying for a full LLM generation run.
|
|
24
|
+
*
|
|
25
|
+
* Also emits a READ report: how many endpoints resolved a request / response
|
|
26
|
+
* body, how many carry query strings, and — critically — how many have a body
|
|
27
|
+
* the parser could not resolve (non-JSON / multipart), so nothing is dropped
|
|
28
|
+
* silently.
|
|
29
|
+
*/
|
|
30
|
+
export interface IPreviewMeta {
|
|
31
|
+
title: string;
|
|
32
|
+
version: string;
|
|
33
|
+
serverUrl: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function renderPreviewHtml(
|
|
37
|
+
document: OpenApi.IDocument,
|
|
38
|
+
meta: IPreviewMeta,
|
|
39
|
+
filter: IEndpointFilter = {},
|
|
40
|
+
): string {
|
|
41
|
+
const endpoints = filterEndpoints(toEndpoints(document), filter);
|
|
42
|
+
const unsupported = unsupportedOperations(document);
|
|
43
|
+
const groups = classifyEndpoints(endpoints, document);
|
|
44
|
+
const stats = computeStats(endpoints);
|
|
45
|
+
|
|
46
|
+
const sections = groups
|
|
47
|
+
.map((group) => renderResource(group, document))
|
|
48
|
+
.join("\n");
|
|
49
|
+
|
|
50
|
+
const droppedSection =
|
|
51
|
+
unsupported.length === 0
|
|
52
|
+
? ""
|
|
53
|
+
: `<section class="resource">
|
|
54
|
+
<h2 class="warn">dropped <small>${unsupported.length}</small></h2>
|
|
55
|
+
<p class="note">Operations HttpMigration could not parse (unsupported content type — raw binary upload/download). Listed so they are not lost silently; they need a hand-written page.</p>
|
|
56
|
+
${unsupported
|
|
57
|
+
.map(
|
|
58
|
+
(u) => `<div class="endpoint"><div class="body" style="padding:.6rem">
|
|
59
|
+
<span class="method method-${u.method.toLowerCase()}">${esc(u.method)}</span>
|
|
60
|
+
<code class="path">${esc(u.path)}</code>
|
|
61
|
+
<span class="badge badge-warn">dropped</span>
|
|
62
|
+
<div class="detail muted" style="margin-top:.4rem">${esc(u.reason)}</div>
|
|
63
|
+
</div></div>`,
|
|
64
|
+
)
|
|
65
|
+
.join("\n")}
|
|
66
|
+
</section>`;
|
|
67
|
+
|
|
68
|
+
return `<!doctype html>
|
|
69
|
+
<html lang="en">
|
|
70
|
+
<head>
|
|
71
|
+
<meta charset="utf-8" />
|
|
72
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
73
|
+
<title>${esc(meta.title)} — AutoView preview</title>
|
|
74
|
+
<style>${STYLE}</style>
|
|
75
|
+
</head>
|
|
76
|
+
<body>
|
|
77
|
+
<header>
|
|
78
|
+
<h1>${esc(meta.title)} <small>v${esc(meta.version)}</small></h1>
|
|
79
|
+
<p class="server">${
|
|
80
|
+
meta.serverUrl
|
|
81
|
+
? `backend: <code>${esc(meta.serverUrl)}</code>`
|
|
82
|
+
: `<span class="warn">no <code>servers[]</code> — frontend would boot in simulator mode</span>`
|
|
83
|
+
}</p>
|
|
84
|
+
</header>
|
|
85
|
+
<section class="report">
|
|
86
|
+
<h2>READ report</h2>
|
|
87
|
+
<div class="cards">
|
|
88
|
+
<div class="card"><b>${stats.total}</b><span>endpoints</span></div>
|
|
89
|
+
<div class="card"><b>${groups.length}</b><span>resources</span></div>
|
|
90
|
+
<div class="card"><b>${stats.withQuery}</b><span>query endpoints</span></div>
|
|
91
|
+
<div class="card"><b>${stats.reqBody}</b><span>request bodies</span></div>
|
|
92
|
+
<div class="card"><b>${stats.resBody}</b><span>response bodies</span></div>
|
|
93
|
+
<div class="card ${stats.unresolvedBody > 0 ? "card-warn" : ""}"><b>${stats.unresolvedBody}</b><span>unresolved body*</span></div>
|
|
94
|
+
<div class="card ${unsupported.length > 0 ? "card-warn" : ""}"><b>${unsupported.length}</b><span>dropped**</span></div>
|
|
95
|
+
</div>
|
|
96
|
+
<p class="note">* write operations whose request body the READ layer could not resolve (typically multipart). Marked <span class="badge badge-warn">no-body</span> below.<br/>** operations HttpMigration could not parse at all (unsupported content type). Listed in the <b>dropped</b> section so nothing is lost silently.</p>
|
|
97
|
+
</section>
|
|
98
|
+
${sections}
|
|
99
|
+
${droppedSection}
|
|
100
|
+
<footer>Generated by AutoView <code>--preview</code> — READ layer only, no LLM. Run the full command (without <code>--preview</code>) to generate the frontend.</footer>
|
|
101
|
+
</body>
|
|
102
|
+
</html>`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface IStats {
|
|
106
|
+
total: number;
|
|
107
|
+
withQuery: number;
|
|
108
|
+
reqBody: number;
|
|
109
|
+
resBody: number;
|
|
110
|
+
unresolvedBody: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function computeStats(endpoints: IAutoViewEndpoint[]): IStats {
|
|
114
|
+
let withQuery = 0;
|
|
115
|
+
let reqBody = 0;
|
|
116
|
+
let resBody = 0;
|
|
117
|
+
let unresolvedBody = 0;
|
|
118
|
+
const bodyMethods = new Set(["post", "put", "patch"]);
|
|
119
|
+
for (const ep of endpoints) {
|
|
120
|
+
if (ep.query !== null) withQuery++;
|
|
121
|
+
if (ep.requestBody !== null) reqBody++;
|
|
122
|
+
if (ep.responseBody !== null) resBody++;
|
|
123
|
+
// A write operation with no resolvable request body is a candidate for a
|
|
124
|
+
// silently-dropped non-JSON / multipart payload.
|
|
125
|
+
if (bodyMethods.has(ep.method.toLowerCase()) && ep.requestBody === null) {
|
|
126
|
+
unresolvedBody++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { total: endpoints.length, withQuery, reqBody, resBody, unresolvedBody };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderResource(
|
|
133
|
+
group: IResourceGroup,
|
|
134
|
+
document: OpenApi.IDocument,
|
|
135
|
+
): string {
|
|
136
|
+
const rows = group.endpoints
|
|
137
|
+
.map((c) => renderEndpoint(c, document))
|
|
138
|
+
.join("\n");
|
|
139
|
+
// Deterministic screen set this resource produces — actual Next.js paths.
|
|
140
|
+
const screens = planResource(group);
|
|
141
|
+
const plannedHtml = screens
|
|
142
|
+
.map(
|
|
143
|
+
(s) =>
|
|
144
|
+
`<span class="screen screen-${s.kind}"><code>${esc(s.path)}</code> <i>${s.kind}</i></span>`,
|
|
145
|
+
)
|
|
146
|
+
.join("");
|
|
147
|
+
return `<section class="resource">
|
|
148
|
+
<h2>${esc(group.resource)} <small>${group.endpoints.length}</small></h2>
|
|
149
|
+
<div class="planned">screens: ${plannedHtml || "<i class='muted'>none (no list/detail/create/update)</i>"}</div>
|
|
150
|
+
${rows}
|
|
151
|
+
</section>`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderEndpoint(
|
|
155
|
+
classified: IClassifiedEndpoint,
|
|
156
|
+
document: OpenApi.IDocument,
|
|
157
|
+
): string {
|
|
158
|
+
const ep = classified.endpoint;
|
|
159
|
+
const method = ep.method.toLowerCase();
|
|
160
|
+
const bodyMethods = new Set(["post", "put", "patch"]);
|
|
161
|
+
const unresolved =
|
|
162
|
+
bodyMethods.has(method) && ep.requestBody === null
|
|
163
|
+
? `<span class="badge badge-warn">no-body</span>`
|
|
164
|
+
: "";
|
|
165
|
+
const queryBadge =
|
|
166
|
+
ep.query !== null ? `<span class="badge badge-q">query</span>` : "";
|
|
167
|
+
|
|
168
|
+
const details: string[] = [];
|
|
169
|
+
if (ep.parameters.length > 0) {
|
|
170
|
+
details.push(
|
|
171
|
+
`<div class="detail"><b>path</b> ${ep.parameters
|
|
172
|
+
.map((p) => `<code>${esc(p.name)}</code>`)
|
|
173
|
+
.join(" ")}</div>`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (ep.query !== null) {
|
|
177
|
+
details.push(
|
|
178
|
+
`<div class="detail"><b>query</b><pre>${esc(renderSchemaInline(ep.query, document))}</pre></div>`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (ep.requestBody !== null) {
|
|
182
|
+
details.push(
|
|
183
|
+
`<div class="detail"><b>request</b> <code>${esc(ep.requestBody.typeName)}</code><pre>${esc(renderNamedSchema(ep.requestBody.typeName, document))}</pre></div>`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (ep.responseBody !== null) {
|
|
187
|
+
details.push(
|
|
188
|
+
`<div class="detail"><b>response</b> <code>${esc(ep.responseBody.typeName)}</code><pre>${esc(renderNamedSchema(ep.responseBody.typeName, document))}</pre></div>`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return `<details class="endpoint">
|
|
193
|
+
<summary>
|
|
194
|
+
<span class="badge role role-${classified.role}">${classified.role}</span>
|
|
195
|
+
<span class="method method-${method}">${esc(ep.method.toUpperCase())}</span>
|
|
196
|
+
<code class="path">${esc(ep.path)}</code>
|
|
197
|
+
${queryBadge}${unresolved}
|
|
198
|
+
<span class="accessor">${esc(ep.accessor.join("."))}</span>
|
|
199
|
+
</summary>
|
|
200
|
+
<div class="body">
|
|
201
|
+
${ep.description ? `<p class="desc">${esc(ep.description)}</p>` : ""}
|
|
202
|
+
${details.join("\n ") || '<div class="detail muted">no params / body</div>'}
|
|
203
|
+
</div>
|
|
204
|
+
</details>`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The query schema is an inline object (not a $ref), so render it directly via
|
|
208
|
+
// renderNamedSchema's sibling path — but renderNamedSchema only takes a type
|
|
209
|
+
// name. For inline query schemas we stringify the JSON shape compactly.
|
|
210
|
+
function renderSchemaInline(
|
|
211
|
+
schema: OpenApi.IJsonSchema,
|
|
212
|
+
_document: OpenApi.IDocument,
|
|
213
|
+
): string {
|
|
214
|
+
if ("properties" in schema && schema.properties) {
|
|
215
|
+
const required = new Set(schema.required ?? []);
|
|
216
|
+
return Object.entries(schema.properties)
|
|
217
|
+
.map(([name, prop]) => {
|
|
218
|
+
const opt = required.has(name) ? "" : "?";
|
|
219
|
+
const t = "type" in prop ? String(prop.type) : "unknown";
|
|
220
|
+
return `${name}${opt}: ${t}`;
|
|
221
|
+
})
|
|
222
|
+
.join("\n");
|
|
223
|
+
}
|
|
224
|
+
return "type" in schema ? String(schema.type) : "unknown";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function esc(s: string): string {
|
|
228
|
+
return s
|
|
229
|
+
.replace(/&/g, "&")
|
|
230
|
+
.replace(/</g, "<")
|
|
231
|
+
.replace(/>/g, ">");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const STYLE = `
|
|
235
|
+
:root { color-scheme: light dark; }
|
|
236
|
+
* { box-sizing: border-box; }
|
|
237
|
+
body { font: 14px/1.5 ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 0 0 4rem; max-width: 900px; margin-inline: auto; }
|
|
238
|
+
header { padding: 2rem 1.5rem 1rem; border-bottom: 1px solid #8883; }
|
|
239
|
+
h1 { margin: 0; font-size: 1.6rem; } h1 small { font-weight: 400; opacity: .55; font-size: .9rem; }
|
|
240
|
+
.server { margin: .4rem 0 0; opacity: .8; } .warn { color: #c80; }
|
|
241
|
+
code { font-family: ui-monospace, monospace; background: #8881; padding: .05em .35em; border-radius: 4px; }
|
|
242
|
+
.report { padding: 1rem 1.5rem; }
|
|
243
|
+
.report h2, .resource h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: .05em; opacity: .6; }
|
|
244
|
+
.cards { display: flex; flex-wrap: wrap; gap: .6rem; }
|
|
245
|
+
.card { background: #8881; border-radius: 8px; padding: .6rem .9rem; min-width: 90px; }
|
|
246
|
+
.card b { display: block; font-size: 1.5rem; } .card span { opacity: .65; font-size: .8rem; }
|
|
247
|
+
.card-warn { background: #c8001a; color: #fff; } .card-warn span { opacity: .85; }
|
|
248
|
+
.note { font-size: .8rem; opacity: .6; }
|
|
249
|
+
.resource { padding: .3rem 1.5rem; }
|
|
250
|
+
.endpoint { border: 1px solid #8883; border-radius: 8px; margin: .35rem 0; padding: .1rem .2rem; }
|
|
251
|
+
.endpoint summary { cursor: pointer; padding: .5rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
|
|
252
|
+
.method { font-weight: 700; font-size: .72rem; padding: .15em .5em; border-radius: 4px; color: #fff; }
|
|
253
|
+
.method-get { background: #2563eb; } .method-post { background: #16a34a; }
|
|
254
|
+
.method-put, .method-patch { background: #d97706; } .method-delete { background: #dc2626; }
|
|
255
|
+
.path { font-size: .95rem; } .accessor { margin-left: auto; opacity: .45; font-size: .8rem; font-family: ui-monospace, monospace; }
|
|
256
|
+
.badge { font-size: .68rem; padding: .1em .45em; border-radius: 4px; }
|
|
257
|
+
.badge-q { background: #7c3aed; color: #fff; } .badge-warn { background: #c8001a; color: #fff; }
|
|
258
|
+
.roles, .planned { padding: .1rem 0 .5rem; opacity: .9; font-size: .8rem; }
|
|
259
|
+
.planned .screen { display: inline-block; margin: .15rem .3rem .15rem 0; padding: .1rem .4rem; border-radius: 5px; background: #8881; }
|
|
260
|
+
.planned .screen i { opacity: .55; font-style: normal; font-size: .72rem; text-transform: uppercase; }
|
|
261
|
+
.screen-list { border-left: 3px solid #2563eb; } .screen-detail { border-left: 3px solid #0891b2; }
|
|
262
|
+
.screen-create { border-left: 3px solid #16a34a; } .screen-edit { border-left: 3px solid #d97706; }
|
|
263
|
+
.role { font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
|
|
264
|
+
.role-list { background: #2563eb; color: #fff; } .role-search { background: #7c3aed; color: #fff; }
|
|
265
|
+
.role-detail { background: #0891b2; color: #fff; } .role-create { background: #16a34a; color: #fff; }
|
|
266
|
+
.role-update { background: #d97706; color: #fff; } .role-delete { background: #dc2626; color: #fff; }
|
|
267
|
+
.role-action { background: #64748b; color: #fff; }
|
|
268
|
+
.body { padding: 0 .6rem .6rem; } .desc { opacity: .75; margin: .2rem 0 .6rem; }
|
|
269
|
+
.detail { margin: .3rem 0; } .detail b { display: inline-block; min-width: 4.5rem; opacity: .55; font-size: .8rem; text-transform: uppercase; }
|
|
270
|
+
.detail pre { margin: .2rem 0 0; background: #8881; padding: .5rem .7rem; border-radius: 6px; overflow-x: auto; font-size: .82rem; }
|
|
271
|
+
.muted { opacity: .5; }
|
|
272
|
+
footer { padding: 2rem 1.5rem; opacity: .5; font-size: .8rem; border-top: 1px solid #8883; margin-top: 1rem; }
|
|
273
|
+
`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Narrow self-defined view of the AutoBE compiler. AutoView only calls
|
|
5
|
+
* `compiler.interface.write(document, exclude)` to materialize the SDK source
|
|
6
|
+
* it shows the LLM, so only that surface is reproduced here.
|
|
7
|
+
*
|
|
8
|
+
* Step 3 of the standalone track replaces the underlying `@autobe/compiler`
|
|
9
|
+
* runtime with a standard-OpenAPI SDK generator; this interface is the seam
|
|
10
|
+
* that change happens behind.
|
|
11
|
+
*/
|
|
12
|
+
export interface IAutoBeCompiler {
|
|
13
|
+
interface: IAutoBeInterfaceCompiler;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IAutoBeInterfaceCompiler {
|
|
17
|
+
/**
|
|
18
|
+
* Generate the SDK / NestJS project source for an OpenAPI document.
|
|
19
|
+
*
|
|
20
|
+
* @param document AutoBE OpenAPI AST document
|
|
21
|
+
* @param options Either an exclude list, or a `{ language, exclude }` object
|
|
22
|
+
* @returns `path → content` map of the generated project
|
|
23
|
+
*/
|
|
24
|
+
write(
|
|
25
|
+
document: OpenApi.IDocument,
|
|
26
|
+
options:
|
|
27
|
+
| string[]
|
|
28
|
+
| {
|
|
29
|
+
language: "typescript" | "java";
|
|
30
|
+
exclude: string[];
|
|
31
|
+
},
|
|
32
|
+
): Promise<Record<string, string>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Compiler event listener. AutoView hands the compiler a no-op sink (no UI to
|
|
37
|
+
* forward realize-test events to), so only the nested shape the sink fills is
|
|
38
|
+
* modeled. Loosely typed because the standalone path never reads the args.
|
|
39
|
+
*/
|
|
40
|
+
export interface IAutoBeCompilerListener {
|
|
41
|
+
realize: {
|
|
42
|
+
test: {
|
|
43
|
+
onOperation: (...args: unknown[]) => Promise<void>;
|
|
44
|
+
onReset: (...args: unknown[]) => Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|