@hstm-labs/forge-api-generator 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/api-generate-stage.d.ts +48 -0
- package/dist/api-generate-stage.d.ts.map +1 -0
- package/dist/api-generate-stage.js +164 -0
- package/dist/api-generate-stage.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/validator.d.ts +42 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +236 -0
- package/dist/validator.js.map +1 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @hstm-labs/forge-api-generator
|
|
2
|
+
|
|
3
|
+
API layer generation stage for Forge — produces endpoint definitions, request/response handlers, type definitions, and API contracts from architecture output.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @hstm-labs/forge-api-generator
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Public API
|
|
12
|
+
|
|
13
|
+
### Types
|
|
14
|
+
|
|
15
|
+
- `ApiArtifact` — complete API layer output
|
|
16
|
+
- `ApiEndpoint` — route definition with method, path, parameters
|
|
17
|
+
- `ApiHandler` — request handler implementation
|
|
18
|
+
- `ApiTypeDefinition` — shared type/interface definitions
|
|
19
|
+
- `ApiContract` — API contract (OpenAPI-style) metadata
|
|
20
|
+
|
|
21
|
+
### Classes
|
|
22
|
+
|
|
23
|
+
- `ApiGenerateStage` — pipeline stage implementing `PipelineStage` interface
|
|
24
|
+
- `ApiOutputValidator` — validates LLM-produced API output
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { ApiGenerateStage } from '@hstm-labs/forge-api-generator';
|
|
30
|
+
|
|
31
|
+
const stage = new ApiGenerateStage();
|
|
32
|
+
const result = await stage.execute(input);
|
|
33
|
+
// result.data contains ApiArtifact
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API generation pipeline stage.
|
|
3
|
+
*
|
|
4
|
+
* Produces routes/resolvers, handlers, type definitions, and a contract
|
|
5
|
+
* artifact (OpenAPI/GraphQL schema) from specification, architecture, and
|
|
6
|
+
* profile. Follows the same stage implementation pattern established by
|
|
7
|
+
* {@link ArchitectStage} in Task 5.1.
|
|
8
|
+
*
|
|
9
|
+
* In API mode, renders prompt templates, calls the LLM via
|
|
10
|
+
* {@link ApiStageExecutor}, validates the output, unpacks individual
|
|
11
|
+
* files, and returns the parsed {@link ApiArtifact} in the stage output data.
|
|
12
|
+
*
|
|
13
|
+
* In agent mode, the pipeline runner intercepts before calling
|
|
14
|
+
* `execute()` and exports a prompt via {@link AgentStageExecutor}.
|
|
15
|
+
*/
|
|
16
|
+
import type { StageName } from '@hstm-labs/forge-common';
|
|
17
|
+
import type { PipelineStage, PipelineStageInput, PipelineStageOutput, PipelineContext } from '@hstm-labs/forge-core';
|
|
18
|
+
/**
|
|
19
|
+
* Pipeline stage that generates the API layer.
|
|
20
|
+
*
|
|
21
|
+
* Produces an {@link ApiArtifact} containing endpoint definitions, handler
|
|
22
|
+
* source files, type definitions, and a contract artifact. Each handler
|
|
23
|
+
* and type definition is written as a separate file artifact.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const stage = new ApiGenerateStage();
|
|
28
|
+
* const output = await stage.execute(input, context);
|
|
29
|
+
* const api = output.data?.api as ApiArtifact;
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class ApiGenerateStage implements PipelineStage {
|
|
33
|
+
readonly name: StageName;
|
|
34
|
+
readonly dependsOn: StageName[];
|
|
35
|
+
readonly requiresLLM = true;
|
|
36
|
+
/**
|
|
37
|
+
* Execute the API generation stage.
|
|
38
|
+
*
|
|
39
|
+
* @param input - Input from prior stages (expects validate + architect output)
|
|
40
|
+
* @param context - Pipeline context with config, workspace, and adapter
|
|
41
|
+
* @returns Stage output with API artifacts and parsed ApiArtifact in data
|
|
42
|
+
* @throws {ForgeError} FORGE-PIPE-003 if dependency stage output is missing
|
|
43
|
+
* @throws {ForgeError} FORGE-PIPE-001 if adapter is missing
|
|
44
|
+
* @throws {ForgeError} FORGE-GEN-003 if max retries exhausted
|
|
45
|
+
*/
|
|
46
|
+
execute(input: PipelineStageInput, context: PipelineContext): Promise<PipelineStageOutput>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=api-generate-stage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-generate-stage.d.ts","sourceRoot":"","sources":["../src/api-generate-stage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAW/B;;;;;;;;;;;;;GAaG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IACpD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAkB;IAC1C,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,CAAiB;IAChD,QAAQ,CAAC,WAAW,QAAQ;IAE5B;;;;;;;;;OASG;IACG,OAAO,CACX,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC;CAmKhC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API generation pipeline stage.
|
|
3
|
+
*
|
|
4
|
+
* Produces routes/resolvers, handlers, type definitions, and a contract
|
|
5
|
+
* artifact (OpenAPI/GraphQL schema) from specification, architecture, and
|
|
6
|
+
* profile. Follows the same stage implementation pattern established by
|
|
7
|
+
* {@link ArchitectStage} in Task 5.1.
|
|
8
|
+
*
|
|
9
|
+
* In API mode, renders prompt templates, calls the LLM via
|
|
10
|
+
* {@link ApiStageExecutor}, validates the output, unpacks individual
|
|
11
|
+
* files, and returns the parsed {@link ApiArtifact} in the stage output data.
|
|
12
|
+
*
|
|
13
|
+
* In agent mode, the pipeline runner intercepts before calling
|
|
14
|
+
* `execute()` and exports a prompt via {@link AgentStageExecutor}.
|
|
15
|
+
*/
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
18
|
+
import { ForgeError, ErrorCodes, hashContent } from '@hstm-labs/forge-common';
|
|
19
|
+
import { ApiStageExecutor } from '@hstm-labs/forge-core';
|
|
20
|
+
import { resolveApiStyle } from '@hstm-labs/forge-architect';
|
|
21
|
+
import { loadProfile } from '@hstm-labs/forge-profiles';
|
|
22
|
+
import { loadTemplate, renderTemplate } from '@hstm-labs/forge-templates';
|
|
23
|
+
import { ApiOutputValidator } from './validator.js';
|
|
24
|
+
/**
|
|
25
|
+
* Pipeline stage that generates the API layer.
|
|
26
|
+
*
|
|
27
|
+
* Produces an {@link ApiArtifact} containing endpoint definitions, handler
|
|
28
|
+
* source files, type definitions, and a contract artifact. Each handler
|
|
29
|
+
* and type definition is written as a separate file artifact.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const stage = new ApiGenerateStage();
|
|
34
|
+
* const output = await stage.execute(input, context);
|
|
35
|
+
* const api = output.data?.api as ApiArtifact;
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class ApiGenerateStage {
|
|
39
|
+
name = 'api-generate';
|
|
40
|
+
dependsOn = ['architect'];
|
|
41
|
+
requiresLLM = true;
|
|
42
|
+
/**
|
|
43
|
+
* Execute the API generation stage.
|
|
44
|
+
*
|
|
45
|
+
* @param input - Input from prior stages (expects validate + architect output)
|
|
46
|
+
* @param context - Pipeline context with config, workspace, and adapter
|
|
47
|
+
* @returns Stage output with API artifacts and parsed ApiArtifact in data
|
|
48
|
+
* @throws {ForgeError} FORGE-PIPE-003 if dependency stage output is missing
|
|
49
|
+
* @throws {ForgeError} FORGE-PIPE-001 if adapter is missing
|
|
50
|
+
* @throws {ForgeError} FORGE-GEN-003 if max retries exhausted
|
|
51
|
+
*/
|
|
52
|
+
async execute(input, context) {
|
|
53
|
+
// 1. Get parsed spec from validate stage output
|
|
54
|
+
const validateOutput = input['validate'];
|
|
55
|
+
if (validateOutput === undefined) {
|
|
56
|
+
throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "API generate stage requires 'validate' stage output, but it was not found. " +
|
|
57
|
+
'Ensure the validate stage runs before the api-generate stage.');
|
|
58
|
+
}
|
|
59
|
+
const parsedSpec = validateOutput.data?.['parsedSpec'];
|
|
60
|
+
if (parsedSpec === undefined) {
|
|
61
|
+
throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Validate stage did not produce a parsed specification in its output data. ' +
|
|
62
|
+
'Ensure the validate stage includes parsedSpec in its data output.');
|
|
63
|
+
}
|
|
64
|
+
// 2. Get architecture from architect stage output
|
|
65
|
+
const architectOutput = input['architect'];
|
|
66
|
+
if (architectOutput === undefined) {
|
|
67
|
+
throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "API generate stage requires 'architect' stage output, but it was not found. " +
|
|
68
|
+
'Ensure the architect stage runs before the api-generate stage.');
|
|
69
|
+
}
|
|
70
|
+
const architecture = architectOutput.data?.['architecture'];
|
|
71
|
+
if (architecture === undefined) {
|
|
72
|
+
throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Architect stage did not produce an architecture in its output data. ' +
|
|
73
|
+
'Ensure the architect stage includes architecture in its data output.');
|
|
74
|
+
}
|
|
75
|
+
// 3. Load profile, resolve API style
|
|
76
|
+
const profile = loadProfile(context.config.profileName, context.workspace.rootDir);
|
|
77
|
+
const resolvedApiStyle = resolveApiStyle(parsedSpec, profile);
|
|
78
|
+
// 4. Compute output directory
|
|
79
|
+
const outputDir = join(context.workspace.forgeDir, 'runs', context.runId, 'stages', 'api-generate', 'artifacts');
|
|
80
|
+
// 5. Load and render templates
|
|
81
|
+
const systemTemplate = loadTemplate('api-generate-system', context.workspace.rootDir);
|
|
82
|
+
const userTemplate = loadTemplate('api-generate-user', context.workspace.rootDir);
|
|
83
|
+
const templateContext = {
|
|
84
|
+
spec: parsedSpec,
|
|
85
|
+
profile: profile,
|
|
86
|
+
architecture: architecture,
|
|
87
|
+
stage: {
|
|
88
|
+
resolvedApiStyle,
|
|
89
|
+
outputDir,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const systemPrompt = renderTemplate(systemTemplate, templateContext);
|
|
93
|
+
const userPrompt = renderTemplate(userTemplate, templateContext);
|
|
94
|
+
// 6. Execute via API mode StageExecutor
|
|
95
|
+
if (context.adapter === undefined) {
|
|
96
|
+
throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'API generate stage requires an LLM adapter in API mode, but none was provided. ' +
|
|
97
|
+
'Configure an LLM provider in forge.config.json or use agent mode.');
|
|
98
|
+
}
|
|
99
|
+
const validator = new ApiOutputValidator(architecture.dataModel);
|
|
100
|
+
const executor = new ApiStageExecutor({
|
|
101
|
+
adapter: context.adapter,
|
|
102
|
+
validator,
|
|
103
|
+
retryPolicy: {
|
|
104
|
+
maxRetries: 3,
|
|
105
|
+
backoffMs: 1000,
|
|
106
|
+
includeErrorInRetry: true,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const result = await executor.execute({
|
|
110
|
+
prompt: userPrompt.content,
|
|
111
|
+
systemPrompt: systemPrompt.content,
|
|
112
|
+
stageName: 'api-generate',
|
|
113
|
+
outputDir,
|
|
114
|
+
runId: context.runId,
|
|
115
|
+
mode: 'api',
|
|
116
|
+
outputSchema: { format: 'json' },
|
|
117
|
+
});
|
|
118
|
+
// 7. Parse ApiArtifact from LLM output
|
|
119
|
+
const rawContent = result.artifacts[0]?.content ?? '';
|
|
120
|
+
const apiArtifact = JSON.parse(rawContent);
|
|
121
|
+
// 8. Write individual files to artifacts directory
|
|
122
|
+
mkdirSync(outputDir, { recursive: true });
|
|
123
|
+
const artifacts = [];
|
|
124
|
+
// Write handler files
|
|
125
|
+
for (const handler of apiArtifact.handlers) {
|
|
126
|
+
const filePath = handler.fileName;
|
|
127
|
+
writeFileSync(join(outputDir, filePath), handler.content, 'utf-8');
|
|
128
|
+
artifacts.push({
|
|
129
|
+
filePath,
|
|
130
|
+
content: handler.content,
|
|
131
|
+
contentHash: hashContent(handler.content),
|
|
132
|
+
sizeBytes: Buffer.byteLength(handler.content, 'utf-8'),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Write type definition files
|
|
136
|
+
for (const typeDef of apiArtifact.types) {
|
|
137
|
+
const filePath = typeDef.fileName;
|
|
138
|
+
writeFileSync(join(outputDir, filePath), typeDef.content, 'utf-8');
|
|
139
|
+
artifacts.push({
|
|
140
|
+
filePath,
|
|
141
|
+
content: typeDef.content,
|
|
142
|
+
contentHash: hashContent(typeDef.content),
|
|
143
|
+
sizeBytes: Buffer.byteLength(typeDef.content, 'utf-8'),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// Write contract artifact
|
|
147
|
+
const contractPath = apiArtifact.contract.fileName;
|
|
148
|
+
writeFileSync(join(outputDir, contractPath), apiArtifact.contract.content, 'utf-8');
|
|
149
|
+
artifacts.push({
|
|
150
|
+
filePath: contractPath,
|
|
151
|
+
content: apiArtifact.contract.content,
|
|
152
|
+
contentHash: hashContent(apiArtifact.contract.content),
|
|
153
|
+
sizeBytes: Buffer.byteLength(apiArtifact.contract.content, 'utf-8'),
|
|
154
|
+
});
|
|
155
|
+
// 9. Return PipelineStageOutput with all artifacts and data
|
|
156
|
+
return {
|
|
157
|
+
artifacts,
|
|
158
|
+
data: {
|
|
159
|
+
api: apiArtifact,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=api-generate-stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-generate-stage.js","sourceRoot":"","sources":["../src/api-generate-stage.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAU5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API artifact type definitions for the Forge API generation stage.
|
|
3
|
+
*
|
|
4
|
+
* Defines the output schema produced by the API generation stage,
|
|
5
|
+
* including endpoints, handlers, type definitions, and the contract
|
|
6
|
+
* artifact (OpenAPI or GraphQL schema).
|
|
7
|
+
*/
|
|
8
|
+
/** Complete API layer output produced by the api-generate stage. */
|
|
9
|
+
export interface ApiArtifact {
|
|
10
|
+
/** Resolved API style for this generation. */
|
|
11
|
+
apiStyle: 'rest' | 'graphql' | 'grpc';
|
|
12
|
+
/** Generated route/resolver definitions. */
|
|
13
|
+
endpoints: ApiEndpoint[];
|
|
14
|
+
/** Generated handler implementations (source code). */
|
|
15
|
+
handlers: ApiHandler[];
|
|
16
|
+
/** Type/schema definitions. */
|
|
17
|
+
types: ApiTypeDefinition[];
|
|
18
|
+
/** Contract artifact content. */
|
|
19
|
+
contract: ApiContract;
|
|
20
|
+
}
|
|
21
|
+
/** A single API endpoint or resolver definition. */
|
|
22
|
+
export interface ApiEndpoint {
|
|
23
|
+
/** Entity or resource this endpoint covers. */
|
|
24
|
+
entity: string;
|
|
25
|
+
/** HTTP method (REST) or operation type (GraphQL: query/mutation). */
|
|
26
|
+
method: string;
|
|
27
|
+
/** Route path (REST: /api/pets) or field name (GraphQL: pets). */
|
|
28
|
+
path: string;
|
|
29
|
+
/** Handler function name. */
|
|
30
|
+
handler: string;
|
|
31
|
+
/** Description. */
|
|
32
|
+
description: string;
|
|
33
|
+
}
|
|
34
|
+
/** A generated handler source file. */
|
|
35
|
+
export interface ApiHandler {
|
|
36
|
+
/** File name (e.g., "pet-router.ts", "pet-resolver.ts"). */
|
|
37
|
+
fileName: string;
|
|
38
|
+
/** File content (source code). */
|
|
39
|
+
content: string;
|
|
40
|
+
/** Entity this handler covers. */
|
|
41
|
+
entity: string;
|
|
42
|
+
}
|
|
43
|
+
/** A generated TypeScript type/interface definition file. */
|
|
44
|
+
export interface ApiTypeDefinition {
|
|
45
|
+
/** Type name. */
|
|
46
|
+
name: string;
|
|
47
|
+
/** File name. */
|
|
48
|
+
fileName: string;
|
|
49
|
+
/** File content. */
|
|
50
|
+
content: string;
|
|
51
|
+
}
|
|
52
|
+
/** The API contract artifact (OpenAPI spec or GraphQL schema). */
|
|
53
|
+
export interface ApiContract {
|
|
54
|
+
/** Contract format. */
|
|
55
|
+
format: 'openapi' | 'graphql';
|
|
56
|
+
/** File name (openapi.json or schema.graphql). */
|
|
57
|
+
fileName: string;
|
|
58
|
+
/** Contract content (JSON string or GraphQL SDL). */
|
|
59
|
+
content: string;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,4CAA4C;IAC5C,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,uDAAuD;IACvD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,+BAA+B;IAC/B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,iCAAiC;IACjC,QAAQ,EAAE,WAAW,CAAC;CACvB;AAMD,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API artifact type definitions for the Forge API generation stage.
|
|
3
|
+
*
|
|
4
|
+
* Defines the output schema produced by the API generation stage,
|
|
5
|
+
* including endpoints, handlers, type definitions, and the contract
|
|
6
|
+
* artifact (OpenAPI or GraphQL schema).
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API output validator for the Forge API generation stage.
|
|
3
|
+
*
|
|
4
|
+
* Validates LLM-generated API output against the
|
|
5
|
+
* {@link ApiArtifact} schema with structural and semantic checks.
|
|
6
|
+
*/
|
|
7
|
+
import type { OutputValidator, OutputSchema, ValidationResult } from '@hstm-labs/forge-core';
|
|
8
|
+
import type { DataModelEntity } from '@hstm-labs/forge-architect';
|
|
9
|
+
/**
|
|
10
|
+
* Validator for API generation stage output.
|
|
11
|
+
*
|
|
12
|
+
* Performs the following checks:
|
|
13
|
+
* 1. JSON parse validation
|
|
14
|
+
* 2. Required top-level keys
|
|
15
|
+
* 3. Non-empty endpoints with required fields
|
|
16
|
+
* 4. Entity coverage (every data model entity has endpoints)
|
|
17
|
+
* 5. Contract presence with valid format, fileName, and content
|
|
18
|
+
* 6. Contract format matches apiStyle
|
|
19
|
+
* 7. Non-empty handlers with fileName and content
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const validator = new ApiOutputValidator(dataModelEntities);
|
|
24
|
+
* const result = validator.validate(llmOutput, { format: 'json' });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class ApiOutputValidator implements OutputValidator {
|
|
28
|
+
private readonly dataModelEntities;
|
|
29
|
+
/**
|
|
30
|
+
* @param dataModelEntities - Data model entities to verify endpoint coverage
|
|
31
|
+
*/
|
|
32
|
+
constructor(dataModelEntities: DataModelEntity[]);
|
|
33
|
+
/**
|
|
34
|
+
* Validate API output against the ApiArtifact schema.
|
|
35
|
+
*
|
|
36
|
+
* @param output - Raw LLM output text
|
|
37
|
+
* @param _schema - Output schema (format expected to be 'json')
|
|
38
|
+
* @returns Validation result with descriptive errors
|
|
39
|
+
*/
|
|
40
|
+
validate(output: string, _schema: OutputSchema): ValidationResult;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAEjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAuBlE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IAEtD;;OAEG;gBACS,iBAAiB,EAAE,eAAe,EAAE;IAIhD;;;;;;OAMG;IAEH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,gBAAgB;CAiMlE"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API output validator for the Forge API generation stage.
|
|
3
|
+
*
|
|
4
|
+
* Validates LLM-generated API output against the
|
|
5
|
+
* {@link ApiArtifact} schema with structural and semantic checks.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Contract format mapping
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const API_STYLE_TO_CONTRACT_FORMAT = {
|
|
11
|
+
rest: 'openapi',
|
|
12
|
+
graphql: 'graphql',
|
|
13
|
+
};
|
|
14
|
+
const REQUIRED_TOP_LEVEL_KEYS = [
|
|
15
|
+
'apiStyle',
|
|
16
|
+
'endpoints',
|
|
17
|
+
'handlers',
|
|
18
|
+
'types',
|
|
19
|
+
'contract',
|
|
20
|
+
];
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Implementation
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Validator for API generation stage output.
|
|
26
|
+
*
|
|
27
|
+
* Performs the following checks:
|
|
28
|
+
* 1. JSON parse validation
|
|
29
|
+
* 2. Required top-level keys
|
|
30
|
+
* 3. Non-empty endpoints with required fields
|
|
31
|
+
* 4. Entity coverage (every data model entity has endpoints)
|
|
32
|
+
* 5. Contract presence with valid format, fileName, and content
|
|
33
|
+
* 6. Contract format matches apiStyle
|
|
34
|
+
* 7. Non-empty handlers with fileName and content
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const validator = new ApiOutputValidator(dataModelEntities);
|
|
39
|
+
* const result = validator.validate(llmOutput, { format: 'json' });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class ApiOutputValidator {
|
|
43
|
+
dataModelEntities;
|
|
44
|
+
/**
|
|
45
|
+
* @param dataModelEntities - Data model entities to verify endpoint coverage
|
|
46
|
+
*/
|
|
47
|
+
constructor(dataModelEntities) {
|
|
48
|
+
this.dataModelEntities = dataModelEntities;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate API output against the ApiArtifact schema.
|
|
52
|
+
*
|
|
53
|
+
* @param output - Raw LLM output text
|
|
54
|
+
* @param _schema - Output schema (format expected to be 'json')
|
|
55
|
+
* @returns Validation result with descriptive errors
|
|
56
|
+
*/
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
58
|
+
validate(output, _schema) {
|
|
59
|
+
const errors = [];
|
|
60
|
+
// 1. Parse as JSON
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(output);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
errors.push({
|
|
67
|
+
message: 'API output is not valid JSON. Ensure the LLM produces only raw JSON without markdown fences or explanatory text.',
|
|
68
|
+
severity: 'error',
|
|
69
|
+
});
|
|
70
|
+
return { valid: false, errors };
|
|
71
|
+
}
|
|
72
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
73
|
+
errors.push({
|
|
74
|
+
message: 'API output must be a JSON object, not an array or primitive.',
|
|
75
|
+
severity: 'error',
|
|
76
|
+
});
|
|
77
|
+
return { valid: false, errors };
|
|
78
|
+
}
|
|
79
|
+
const obj = parsed;
|
|
80
|
+
// 2. Verify required top-level keys
|
|
81
|
+
for (const key of REQUIRED_TOP_LEVEL_KEYS) {
|
|
82
|
+
if (!(key in obj)) {
|
|
83
|
+
errors.push({
|
|
84
|
+
message: `Missing required top-level key: '${key}'.`,
|
|
85
|
+
path: key,
|
|
86
|
+
severity: 'error',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (errors.length > 0) {
|
|
91
|
+
return { valid: false, errors };
|
|
92
|
+
}
|
|
93
|
+
// 3. Verify endpoints is non-empty array with required fields
|
|
94
|
+
const endpoints = obj['endpoints'];
|
|
95
|
+
if (!Array.isArray(endpoints) || endpoints.length === 0) {
|
|
96
|
+
errors.push({
|
|
97
|
+
message: "Key 'endpoints' must be a non-empty array of endpoint definitions.",
|
|
98
|
+
path: 'endpoints',
|
|
99
|
+
severity: 'error',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
for (let i = 0; i < endpoints.length; i++) {
|
|
104
|
+
const ep = endpoints[i];
|
|
105
|
+
if (ep === undefined || typeof ep !== 'object' || ep === null) {
|
|
106
|
+
errors.push({
|
|
107
|
+
message: `endpoints[${String(i)}] is not a valid object.`,
|
|
108
|
+
path: `endpoints[${String(i)}]`,
|
|
109
|
+
severity: 'error',
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (typeof ep['entity'] !== 'string' || ep['entity'].length === 0) {
|
|
114
|
+
errors.push({
|
|
115
|
+
message: `endpoints[${String(i)}] is missing required field 'entity'.`,
|
|
116
|
+
path: `endpoints[${String(i)}].entity`,
|
|
117
|
+
severity: 'error',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (typeof ep['method'] !== 'string' || ep['method'].length === 0) {
|
|
121
|
+
errors.push({
|
|
122
|
+
message: `endpoints[${String(i)}] is missing required field 'method'.`,
|
|
123
|
+
path: `endpoints[${String(i)}].method`,
|
|
124
|
+
severity: 'error',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (typeof ep['path'] !== 'string' || ep['path'].length === 0) {
|
|
128
|
+
errors.push({
|
|
129
|
+
message: `endpoints[${String(i)}] is missing required field 'path'.`,
|
|
130
|
+
path: `endpoints[${String(i)}].path`,
|
|
131
|
+
severity: 'error',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// 4. Entity coverage — every data model entity has at least one endpoint
|
|
137
|
+
if (Array.isArray(endpoints) && endpoints.length > 0) {
|
|
138
|
+
const endpointEntities = new Set(endpoints
|
|
139
|
+
.filter((ep) => typeof ep['entity'] === 'string')
|
|
140
|
+
.map((ep) => ep['entity'].toLowerCase()));
|
|
141
|
+
for (const entity of this.dataModelEntities) {
|
|
142
|
+
if (!endpointEntities.has(entity.name.toLowerCase())) {
|
|
143
|
+
errors.push({
|
|
144
|
+
message: `Data model entity '${entity.name}' has no corresponding endpoints. Every entity must have API coverage.`,
|
|
145
|
+
path: 'endpoints',
|
|
146
|
+
severity: 'error',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// 5. Verify contract has valid format, fileName, and non-empty content
|
|
152
|
+
const contract = obj['contract'];
|
|
153
|
+
if (typeof contract !== 'object' || contract === null || Array.isArray(contract)) {
|
|
154
|
+
errors.push({
|
|
155
|
+
message: "Key 'contract' must be an object with format, fileName, and content.",
|
|
156
|
+
path: 'contract',
|
|
157
|
+
severity: 'error',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const c = contract;
|
|
162
|
+
if (typeof c['format'] !== 'string' || c['format'].length === 0) {
|
|
163
|
+
errors.push({
|
|
164
|
+
message: "Contract is missing required field 'format'.",
|
|
165
|
+
path: 'contract.format',
|
|
166
|
+
severity: 'error',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (typeof c['fileName'] !== 'string' || c['fileName'].length === 0) {
|
|
170
|
+
errors.push({
|
|
171
|
+
message: "Contract is missing required field 'fileName'.",
|
|
172
|
+
path: 'contract.fileName',
|
|
173
|
+
severity: 'error',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (typeof c['content'] !== 'string' || c['content'].length === 0) {
|
|
177
|
+
errors.push({
|
|
178
|
+
message: "Contract is missing required field 'content' (must be non-empty).",
|
|
179
|
+
path: 'contract.content',
|
|
180
|
+
severity: 'error',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// 6. Verify contract format matches apiStyle
|
|
184
|
+
const apiStyle = obj['apiStyle'];
|
|
185
|
+
if (typeof apiStyle === 'string' &&
|
|
186
|
+
typeof c['format'] === 'string') {
|
|
187
|
+
const expectedFormat = API_STYLE_TO_CONTRACT_FORMAT[apiStyle];
|
|
188
|
+
if (expectedFormat !== undefined && c['format'] !== expectedFormat) {
|
|
189
|
+
errors.push({
|
|
190
|
+
message: `Contract format '${c['format']}' does not match API style '${apiStyle}'. Expected '${expectedFormat}'.`,
|
|
191
|
+
path: 'contract.format',
|
|
192
|
+
severity: 'error',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// 7. Verify handlers is non-empty array with fileName and content
|
|
198
|
+
const handlers = obj['handlers'];
|
|
199
|
+
if (!Array.isArray(handlers) || handlers.length === 0) {
|
|
200
|
+
errors.push({
|
|
201
|
+
message: "Key 'handlers' must be a non-empty array of handler definitions.",
|
|
202
|
+
path: 'handlers',
|
|
203
|
+
severity: 'error',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
208
|
+
const h = handlers[i];
|
|
209
|
+
if (h === undefined || typeof h !== 'object' || h === null) {
|
|
210
|
+
errors.push({
|
|
211
|
+
message: `handlers[${String(i)}] is not a valid object.`,
|
|
212
|
+
path: `handlers[${String(i)}]`,
|
|
213
|
+
severity: 'error',
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (typeof h['fileName'] !== 'string' || h['fileName'].length === 0) {
|
|
218
|
+
errors.push({
|
|
219
|
+
message: `handlers[${String(i)}] is missing required field 'fileName'.`,
|
|
220
|
+
path: `handlers[${String(i)}].fileName`,
|
|
221
|
+
severity: 'error',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (typeof h['content'] !== 'string' || h['content'].length === 0) {
|
|
225
|
+
errors.push({
|
|
226
|
+
message: `handlers[${String(i)}] is missing required field 'content'.`,
|
|
227
|
+
path: `handlers[${String(i)}].content`,
|
|
228
|
+
severity: 'error',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { valid: errors.length === 0, errors };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E,MAAM,4BAA4B,GAA2B;IAC3D,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF,MAAM,uBAAuB,GAAG;IAC9B,UAAU;IACV,WAAW;IACX,UAAU;IACV,OAAO;IACP,UAAU;CACF,CAAC;AAEX,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,kBAAkB;IACZ,iBAAiB,CAAoB;IAEtD;;OAEG;IACH,YAAY,iBAAoC;QAC9C,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;IAC7C,CAAC;IAED;;;;;;OAMG;IACH,6DAA6D;IAC7D,QAAQ,CAAC,MAAc,EAAE,OAAqB;QAC5C,MAAM,MAAM,GAAsB,EAAE,CAAC;QAErC,mBAAmB;QACnB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO,EACL,kHAAkH;gBACpH,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;YACH,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAClC,CAAC;QAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO,EACL,8DAA8D;gBAChE,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;YACH,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAClC,CAAC;QAED,MAAM,GAAG,GAAG,MAAiC,CAAC;QAE9C,oCAAoC;QACpC,KAAK,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;YAC1C,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;gBAClB,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,oCAAoC,GAAG,IAAI;oBACpD,IAAI,EAAE,GAAG;oBACT,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAClC,CAAC;QAED,8DAA8D;QAC9D,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO,EACL,oEAAoE;gBACtE,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC1C,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAwC,CAAC;gBAC/D,IAAI,EAAE,KAAK,SAAS,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;oBAC9D,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,0BAA0B;wBACzD,IAAI,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,GAAG;wBAC/B,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBACD,IAAI,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAClE,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,uCAAuC;wBACtE,IAAI,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,UAAU;wBACtC,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;gBACD,IAAI,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAClE,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,uCAAuC;wBACtE,IAAI,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,UAAU;wBACtC,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;gBACD,IAAI,OAAO,EAAE,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC9D,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,qCAAqC;wBACpE,IAAI,EAAE,aAAa,MAAM,CAAC,CAAC,CAAC,QAAQ;wBACpC,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,yEAAyE;QACzE,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAC7B,SAA4C;iBAC1C,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC;iBAChD,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAE,EAAE,CAAC,QAAQ,CAAY,CAAC,WAAW,EAAE,CAAC,CACvD,CAAC;YAEF,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC5C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;oBACrD,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,sBAAsB,MAAM,CAAC,IAAI,wEAAwE;wBAClH,IAAI,EAAE,WAAW;wBACjB,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,uEAAuE;QACvE,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;QACjC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjF,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO,EAAE,sEAAsE;gBAC/E,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,QAAmC,CAAC;YAC9C,IAAI,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChE,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,8CAA8C;oBACvD,IAAI,EAAE,iBAAiB;oBACvB,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;YACL,CAAC;YACD,IAAI,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpE,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,gDAAgD;oBACzD,IAAI,EAAE,mBAAmB;oBACzB,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;YACL,CAAC;YACD,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClE,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,mEAAmE;oBAC5E,IAAI,EAAE,kBAAkB;oBACxB,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;YACL,CAAC;YAED,6CAA6C;YAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;YACjC,IACE,OAAO,QAAQ,KAAK,QAAQ;gBAC5B,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAC/B,CAAC;gBACD,MAAM,cAAc,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;gBAC9D,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,cAAc,EAAE,CAAC;oBACnE,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAW,+BAA+B,QAAQ,gBAAgB,cAAc,IAAI;wBAC3H,IAAI,EAAE,iBAAiB;wBACvB,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,kEAAkE;QAClE,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO,EACL,kEAAkE;gBACpE,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAwC,CAAC;gBAC7D,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;oBAC3D,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,YAAY,MAAM,CAAC,CAAC,CAAC,0BAA0B;wBACxD,IAAI,EAAE,YAAY,MAAM,CAAC,CAAC,CAAC,GAAG;wBAC9B,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBACD,IAAI,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACpE,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,YAAY,MAAM,CAAC,CAAC,CAAC,yCAAyC;wBACvE,IAAI,EAAE,YAAY,MAAM,CAAC,CAAC,CAAC,YAAY;wBACvC,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;gBACD,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAClE,MAAM,CAAC,IAAI,CAAC;wBACV,OAAO,EAAE,YAAY,MAAM,CAAC,CAAC,CAAC,wCAAwC;wBACtE,IAAI,EAAE,YAAY,MAAM,CAAC,CAAC,CAAC,WAAW;wBACtC,QAAQ,EAAE,OAAO;qBAClB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hstm-labs/forge-api-generator",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@hstm-labs/forge-common": "0.1.2",
|
|
20
|
+
"@hstm-labs/forge-core": "0.1.2",
|
|
21
|
+
"@hstm-labs/forge-architect": "0.1.2",
|
|
22
|
+
"@hstm-labs/forge-spec-parser": "0.1.2",
|
|
23
|
+
"@hstm-labs/forge-profiles": "0.1.2",
|
|
24
|
+
"@hstm-labs/forge-templates": "0.1.2"
|
|
25
|
+
}
|
|
26
|
+
}
|