@arpixel/api-contract 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 +21 -0
- package/README.md +699 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +56 -0
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.js +15 -0
- package/dist/commands/generate.d.ts +1 -0
- package/dist/commands/generate.js +19 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +33 -0
- package/dist/diff/comparator.d.ts +3 -0
- package/dist/diff/comparator.js +416 -0
- package/dist/diff/reporter.d.ts +2 -0
- package/dist/diff/reporter.js +32 -0
- package/dist/generator/api.d.ts +1 -0
- package/dist/generator/api.js +106 -0
- package/dist/generator/typescript.d.ts +1 -0
- package/dist/generator/typescript.js +14 -0
- package/dist/openapi/loader.d.ts +2 -0
- package/dist/openapi/loader.js +4 -0
- package/dist/types/diff.d.ts +161 -0
- package/dist/types/diff.js +1 -0
- package/integrations/vscode/api-contract-generate.agent.md +161 -0
- package/integrations/vscode/api-contract-generate.prompt.md +172 -0
- package/integrations/vscode/api-contract-impact.agent.md +193 -0
- package/integrations/vscode/api-contract-impact.prompt.md +285 -0
- package/package.json +44 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { loadOpenApi } from "../openapi/loader.js";
|
|
4
|
+
const METHODS = [
|
|
5
|
+
"get", "post", "put", "patch", "delete", "head", "options"
|
|
6
|
+
];
|
|
7
|
+
function pascalCase(value) {
|
|
8
|
+
return value
|
|
9
|
+
.replace(/[^a-zA-Z0-9]+(.)?/g, (_, c) => c ? c.toUpperCase() : "")
|
|
10
|
+
.replace(/^[a-z]/, c => c.toUpperCase());
|
|
11
|
+
}
|
|
12
|
+
function methodName(path, method, operationId) {
|
|
13
|
+
if (operationId)
|
|
14
|
+
return operationId;
|
|
15
|
+
const parts = path
|
|
16
|
+
.split("/")
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.map(part => part.startsWith("{") ? `By${pascalCase(part.slice(1, -1))}` : pascalCase(part));
|
|
19
|
+
return `${method}${parts.join("") || "Root"}`;
|
|
20
|
+
}
|
|
21
|
+
function schemaType(refOrSchema) {
|
|
22
|
+
if (!refOrSchema || typeof refOrSchema !== "object")
|
|
23
|
+
return "unknown";
|
|
24
|
+
const schema = refOrSchema;
|
|
25
|
+
if (typeof schema.$ref === "string") {
|
|
26
|
+
const match = schema.$ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
27
|
+
if (match)
|
|
28
|
+
return match[1];
|
|
29
|
+
}
|
|
30
|
+
if (typeof schema.type === "string") {
|
|
31
|
+
switch (schema.type) {
|
|
32
|
+
case "string": return "string";
|
|
33
|
+
case "integer":
|
|
34
|
+
case "number": return "number";
|
|
35
|
+
case "boolean": return "boolean";
|
|
36
|
+
case "array": return `${schemaType(schema.items)}[]`;
|
|
37
|
+
case "object": return "Record<string, unknown>";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
function responseType(operation) {
|
|
43
|
+
const responses = operation.responses;
|
|
44
|
+
if (!responses)
|
|
45
|
+
return "unknown";
|
|
46
|
+
const preferred = ["200", "201", "202", "204", "default"];
|
|
47
|
+
const status = preferred.find(code => responses[code]) ?? Object.keys(responses)[0];
|
|
48
|
+
if (!status)
|
|
49
|
+
return "unknown";
|
|
50
|
+
const response = responses[status];
|
|
51
|
+
if (status === "204")
|
|
52
|
+
return "void";
|
|
53
|
+
const content = response.content;
|
|
54
|
+
if (!content)
|
|
55
|
+
return "void";
|
|
56
|
+
const media = content["application/json"] ?? content[Object.keys(content)[0]];
|
|
57
|
+
const schema = media?.schema;
|
|
58
|
+
return schemaType(schema);
|
|
59
|
+
}
|
|
60
|
+
function parameterType(parameter) {
|
|
61
|
+
return schemaType(parameter.schema);
|
|
62
|
+
}
|
|
63
|
+
export async function generateApi(inputPath, outputPath, typesImport = "./types.js") {
|
|
64
|
+
const document = await loadOpenApi(inputPath);
|
|
65
|
+
const paths = document.paths ?? {};
|
|
66
|
+
const lines = [
|
|
67
|
+
"/* eslint-disable */",
|
|
68
|
+
"// Generated by api-contract. Do not edit manually.",
|
|
69
|
+
`import type * as ApiTypes from "${typesImport}";`,
|
|
70
|
+
"",
|
|
71
|
+
"export interface ApiMethods {"
|
|
72
|
+
];
|
|
73
|
+
for (const [path, rawPathItem] of Object.entries(paths)) {
|
|
74
|
+
if (!rawPathItem || typeof rawPathItem !== "object")
|
|
75
|
+
continue;
|
|
76
|
+
const pathItem = rawPathItem;
|
|
77
|
+
const pathParameters = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
|
|
78
|
+
for (const method of METHODS) {
|
|
79
|
+
const rawOperation = pathItem[method];
|
|
80
|
+
if (!rawOperation || typeof rawOperation !== "object")
|
|
81
|
+
continue;
|
|
82
|
+
const operation = rawOperation;
|
|
83
|
+
const name = methodName(path, method, typeof operation.operationId === "string" ? operation.operationId : undefined);
|
|
84
|
+
const parameters = [
|
|
85
|
+
...pathParameters,
|
|
86
|
+
...(Array.isArray(operation.parameters) ? operation.parameters : [])
|
|
87
|
+
];
|
|
88
|
+
const args = parameters
|
|
89
|
+
.filter(p => typeof p === "object")
|
|
90
|
+
.map(p => {
|
|
91
|
+
const param = p;
|
|
92
|
+
const paramName = String(param.name ?? "param");
|
|
93
|
+
const required = Boolean(param.required);
|
|
94
|
+
const type = parameterType(param);
|
|
95
|
+
return `${paramName}${required ? "" : "?"}: ${type}`;
|
|
96
|
+
});
|
|
97
|
+
const result = responseType(operation);
|
|
98
|
+
lines.push(` ${name}(${args.join(", ")}): Promise<${result}>;`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
lines.push("}", "");
|
|
102
|
+
lines.push("// ApiTypes is imported to keep the generated contract connected to the generated OpenAPI types.");
|
|
103
|
+
lines.push("export type OpenApiTypes = typeof ApiTypes;");
|
|
104
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
105
|
+
await writeFile(outputPath, lines.join("\n"), "utf8");
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateTypes(inputPath: string, outputPath: string): Promise<void>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import openapiTS, { astToString } from "openapi-typescript";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { loadOpenApi } from "../openapi/loader.js";
|
|
5
|
+
export async function generateTypes(inputPath, outputPath) {
|
|
6
|
+
const document = await loadOpenApi(inputPath);
|
|
7
|
+
const ast = await openapiTS(document, {
|
|
8
|
+
alphabetize: true,
|
|
9
|
+
enum: true
|
|
10
|
+
});
|
|
11
|
+
const output = astToString(ast);
|
|
12
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
13
|
+
await writeFile(outputPath, output, "utf8");
|
|
14
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export type ChangeType = "ENDPOINT_ADDED" | "ENDPOINT_REMOVED" | "ENDPOINT_MODIFIED" | "PARAMETER_ADDED" | "PARAMETER_REMOVED" | "PARAMETER_TYPE_CHANGED" | "PARAMETER_REQUIRED_CHANGED" | "SCHEMA_ADDED" | "SCHEMA_REMOVED" | "SCHEMA_TYPE_CHANGED" | "PROPERTY_ADDED" | "PROPERTY_REMOVED" | "PROPERTY_TYPE_CHANGED" | "PROPERTY_REQUIRED_CHANGED" | "ENUM_VALUE_ADDED" | "ENUM_VALUE_REMOVED" | "REQUEST_BODY_ADDED" | "REQUEST_BODY_REMOVED" | "REQUEST_BODY_CHANGED" | "RESPONSE_ADDED" | "RESPONSE_REMOVED" | "RESPONSE_CHANGED";
|
|
2
|
+
export interface DiffSummary {
|
|
3
|
+
added: number;
|
|
4
|
+
removed: number;
|
|
5
|
+
modified: number;
|
|
6
|
+
breaking: number;
|
|
7
|
+
}
|
|
8
|
+
export interface EndpointChangeBase {
|
|
9
|
+
location: string;
|
|
10
|
+
path: string;
|
|
11
|
+
method: string;
|
|
12
|
+
detail: string;
|
|
13
|
+
breaking: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface EndpointAddedChange extends EndpointChangeBase {
|
|
16
|
+
type: "ENDPOINT_ADDED";
|
|
17
|
+
}
|
|
18
|
+
export interface EndpointRemovedChange extends EndpointChangeBase {
|
|
19
|
+
type: "ENDPOINT_REMOVED";
|
|
20
|
+
}
|
|
21
|
+
export interface EndpointModifiedChange extends EndpointChangeBase {
|
|
22
|
+
type: "ENDPOINT_MODIFIED";
|
|
23
|
+
from: unknown;
|
|
24
|
+
to: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface ParameterChangeBase extends EndpointChangeBase {
|
|
27
|
+
parameter: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ParameterAddedChange extends ParameterChangeBase {
|
|
30
|
+
type: "PARAMETER_ADDED";
|
|
31
|
+
to: {
|
|
32
|
+
required: boolean;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface ParameterRemovedChange extends ParameterChangeBase {
|
|
36
|
+
type: "PARAMETER_REMOVED";
|
|
37
|
+
from: {
|
|
38
|
+
required: boolean;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface ParameterTypeChangedChange extends ParameterChangeBase {
|
|
42
|
+
type: "PARAMETER_TYPE_CHANGED";
|
|
43
|
+
from: string;
|
|
44
|
+
to: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ParameterRequiredChangedChange extends ParameterChangeBase {
|
|
47
|
+
type: "PARAMETER_REQUIRED_CHANGED";
|
|
48
|
+
from: "required" | "optional";
|
|
49
|
+
to: "required" | "optional";
|
|
50
|
+
}
|
|
51
|
+
export interface SchemaAddedChange {
|
|
52
|
+
type: "SCHEMA_ADDED";
|
|
53
|
+
schema: string;
|
|
54
|
+
location: string;
|
|
55
|
+
detail: string;
|
|
56
|
+
breaking: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface SchemaRemovedChange {
|
|
59
|
+
type: "SCHEMA_REMOVED";
|
|
60
|
+
schema: string;
|
|
61
|
+
location: string;
|
|
62
|
+
detail: string;
|
|
63
|
+
breaking: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface SchemaTypeChangedChange {
|
|
66
|
+
type: "SCHEMA_TYPE_CHANGED";
|
|
67
|
+
schema: string;
|
|
68
|
+
location: string;
|
|
69
|
+
from: string;
|
|
70
|
+
to: string;
|
|
71
|
+
detail: string;
|
|
72
|
+
breaking: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface PropertyChangeBase {
|
|
75
|
+
schema: string;
|
|
76
|
+
property: string;
|
|
77
|
+
location: string;
|
|
78
|
+
detail: string;
|
|
79
|
+
breaking: boolean;
|
|
80
|
+
}
|
|
81
|
+
export interface PropertyAddedChange extends PropertyChangeBase {
|
|
82
|
+
type: "PROPERTY_ADDED";
|
|
83
|
+
required: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface PropertyRemovedChange extends PropertyChangeBase {
|
|
86
|
+
type: "PROPERTY_REMOVED";
|
|
87
|
+
from: "required" | "optional";
|
|
88
|
+
}
|
|
89
|
+
export interface PropertyTypeChangedChange extends PropertyChangeBase {
|
|
90
|
+
type: "PROPERTY_TYPE_CHANGED";
|
|
91
|
+
from: string;
|
|
92
|
+
to: string;
|
|
93
|
+
}
|
|
94
|
+
export interface PropertyRequiredChangedChange extends PropertyChangeBase {
|
|
95
|
+
type: "PROPERTY_REQUIRED_CHANGED";
|
|
96
|
+
from: "required" | "optional";
|
|
97
|
+
to: "required" | "optional";
|
|
98
|
+
}
|
|
99
|
+
export interface EnumValueAddedChange {
|
|
100
|
+
type: "ENUM_VALUE_ADDED";
|
|
101
|
+
schema: string;
|
|
102
|
+
location: string;
|
|
103
|
+
value: unknown;
|
|
104
|
+
detail: string;
|
|
105
|
+
breaking: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface EnumValueRemovedChange {
|
|
108
|
+
type: "ENUM_VALUE_REMOVED";
|
|
109
|
+
schema: string;
|
|
110
|
+
location: string;
|
|
111
|
+
from: unknown;
|
|
112
|
+
detail: string;
|
|
113
|
+
breaking: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface RequestBodyChangeBase {
|
|
116
|
+
path: string;
|
|
117
|
+
method: string;
|
|
118
|
+
location: string;
|
|
119
|
+
detail: string;
|
|
120
|
+
breaking: boolean;
|
|
121
|
+
}
|
|
122
|
+
export interface RequestBodyAddedChange extends RequestBodyChangeBase {
|
|
123
|
+
type: "REQUEST_BODY_ADDED";
|
|
124
|
+
to: string;
|
|
125
|
+
}
|
|
126
|
+
export interface RequestBodyRemovedChange extends RequestBodyChangeBase {
|
|
127
|
+
type: "REQUEST_BODY_REMOVED";
|
|
128
|
+
from: string;
|
|
129
|
+
}
|
|
130
|
+
export interface RequestBodyChangedChange extends RequestBodyChangeBase {
|
|
131
|
+
type: "REQUEST_BODY_CHANGED";
|
|
132
|
+
from: string;
|
|
133
|
+
to: string;
|
|
134
|
+
}
|
|
135
|
+
export interface ResponseChangeBase {
|
|
136
|
+
path: string;
|
|
137
|
+
method: string;
|
|
138
|
+
status: string;
|
|
139
|
+
location: string;
|
|
140
|
+
detail: string;
|
|
141
|
+
breaking: boolean;
|
|
142
|
+
}
|
|
143
|
+
export interface ResponseAddedChange extends ResponseChangeBase {
|
|
144
|
+
type: "RESPONSE_ADDED";
|
|
145
|
+
to: string;
|
|
146
|
+
}
|
|
147
|
+
export interface ResponseRemovedChange extends ResponseChangeBase {
|
|
148
|
+
type: "RESPONSE_REMOVED";
|
|
149
|
+
from: string;
|
|
150
|
+
}
|
|
151
|
+
export interface ResponseChangedChange extends ResponseChangeBase {
|
|
152
|
+
type: "RESPONSE_CHANGED";
|
|
153
|
+
from: string;
|
|
154
|
+
to: string;
|
|
155
|
+
}
|
|
156
|
+
export type ContractChange = EndpointAddedChange | EndpointRemovedChange | EndpointModifiedChange | ParameterAddedChange | ParameterRemovedChange | ParameterTypeChangedChange | ParameterRequiredChangedChange | SchemaAddedChange | SchemaRemovedChange | SchemaTypeChangedChange | PropertyAddedChange | PropertyRemovedChange | PropertyTypeChangedChange | PropertyRequiredChangedChange | EnumValueAddedChange | EnumValueRemovedChange | RequestBodyAddedChange | RequestBodyRemovedChange | RequestBodyChangedChange | ResponseAddedChange | ResponseRemovedChange | ResponseChangedChange;
|
|
157
|
+
export interface DiffResult {
|
|
158
|
+
version: "1";
|
|
159
|
+
summary: DiffSummary;
|
|
160
|
+
changes: ContractChange[];
|
|
161
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
name: API Contract Generate
|
|
4
|
+
description: Generate TypeScript API contract files from an OpenAPI specification using the api-contract CLI.
|
|
5
|
+
-------------------------------------------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
# API Contract Generate Agent
|
|
8
|
+
|
|
9
|
+
You generate TypeScript API contract files from an OpenAPI specification using the `api-contract` CLI.
|
|
10
|
+
|
|
11
|
+
## Responsibility
|
|
12
|
+
|
|
13
|
+
Your job is to:
|
|
14
|
+
|
|
15
|
+
1. Identify the user's OpenAPI input.
|
|
16
|
+
2. Identify the user's requested output directory, if provided.
|
|
17
|
+
3. Run the deterministic `api-contract generate` command.
|
|
18
|
+
4. Verify the generated files.
|
|
19
|
+
5. Report the result.
|
|
20
|
+
|
|
21
|
+
The `api-contract` CLI owns the actual generation logic.
|
|
22
|
+
|
|
23
|
+
Do not manually generate TypeScript from OpenAPI.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Input
|
|
28
|
+
|
|
29
|
+
The user may provide:
|
|
30
|
+
|
|
31
|
+
* an attached OpenAPI file
|
|
32
|
+
* a repository OpenAPI file
|
|
33
|
+
* an explicit OpenAPI path
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
|
|
37
|
+
> Generate types for this openapi.yaml file and output to `src/api/generated-types`.
|
|
38
|
+
|
|
39
|
+
Use the supplied OpenAPI file.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Output Directory
|
|
44
|
+
|
|
45
|
+
If the user specifies an output directory:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
<user-specified-directory>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
use it exactly.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
src/api/generated-types
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
api-contract generate <openapi-file> --output src/api/generated-types
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If no output directory is specified, invoke the CLI without an output option:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
api-contract generate <openapi-file>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The CLI resolves the default output relative to the current working directory:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
api-contract/generated/
|
|
75
|
+
├── types.ts
|
|
76
|
+
└── api.ts
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Do not manually add `--output api-contract/generated` in this case.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Execution
|
|
84
|
+
|
|
85
|
+
When the user specifies an output directory, use:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
api-contract generate <openapi-file> --output <output-directory>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
When no output directory is specified, use:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
api-contract generate <openapi-file>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The CLI is responsible for:
|
|
98
|
+
|
|
99
|
+
* OpenAPI parsing
|
|
100
|
+
* validation
|
|
101
|
+
* TypeScript generation
|
|
102
|
+
* writing generated files
|
|
103
|
+
|
|
104
|
+
Do not reproduce or modify this logic in the agent.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Verification
|
|
109
|
+
|
|
110
|
+
After generation, verify that the output contains:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
types.ts
|
|
114
|
+
api.ts
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Confirm that the generated files contain TypeScript output corresponding to the supplied OpenAPI specification.
|
|
118
|
+
|
|
119
|
+
Do not modify generated files manually.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Modification Policy
|
|
124
|
+
|
|
125
|
+
This agent is allowed to create or replace generated contract files because generation is its explicit purpose.
|
|
126
|
+
|
|
127
|
+
It must not:
|
|
128
|
+
|
|
129
|
+
* modify handwritten application code
|
|
130
|
+
* modify unrelated files
|
|
131
|
+
* silently choose a different output directory
|
|
132
|
+
* manually edit generated output
|
|
133
|
+
|
|
134
|
+
If the user explicitly requests application-code integration after generation, that should be treated as a separate task.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Response
|
|
139
|
+
|
|
140
|
+
Return:
|
|
141
|
+
|
|
142
|
+
# API Contract Generation
|
|
143
|
+
|
|
144
|
+
## Input
|
|
145
|
+
|
|
146
|
+
* OpenAPI: `<path>`
|
|
147
|
+
|
|
148
|
+
## Output
|
|
149
|
+
|
|
150
|
+
* Directory: `<path>`
|
|
151
|
+
|
|
152
|
+
## Generated
|
|
153
|
+
|
|
154
|
+
* `<path>/types.ts`
|
|
155
|
+
* `<path>/api.ts`
|
|
156
|
+
|
|
157
|
+
## Result
|
|
158
|
+
|
|
159
|
+
Briefly state whether generation succeeded.
|
|
160
|
+
|
|
161
|
+
Keep the response concise.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
description: Generate TypeScript API contract files from an OpenAPI specification
|
|
4
|
+
agent: agent
|
|
5
|
+
------------
|
|
6
|
+
|
|
7
|
+
# API Contract Generate
|
|
8
|
+
|
|
9
|
+
Generate TypeScript API contract files from an OpenAPI specification using the `api-contract` CLI.
|
|
10
|
+
|
|
11
|
+
The user may provide:
|
|
12
|
+
|
|
13
|
+
* an OpenAPI file
|
|
14
|
+
* an output directory
|
|
15
|
+
|
|
16
|
+
## Core Principle
|
|
17
|
+
|
|
18
|
+
The `api-contract` CLI performs the actual OpenAPI-to-TypeScript generation.
|
|
19
|
+
|
|
20
|
+
The agent is responsible for:
|
|
21
|
+
|
|
22
|
+
* identifying the OpenAPI input
|
|
23
|
+
* determining the requested output location
|
|
24
|
+
* invoking `api-contract generate`
|
|
25
|
+
* verifying the generated files
|
|
26
|
+
* reporting the result
|
|
27
|
+
|
|
28
|
+
Do not manually generate TypeScript from the OpenAPI specification.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Input
|
|
33
|
+
|
|
34
|
+
The user may provide an OpenAPI file and an optional output directory.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
Generate types for this openapi.yaml file and output to src/api/generated-types.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The OpenAPI file may be:
|
|
43
|
+
|
|
44
|
+
* a repository file
|
|
45
|
+
* an attached file
|
|
46
|
+
* an explicitly provided path
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Output Location
|
|
51
|
+
|
|
52
|
+
### User-specified output
|
|
53
|
+
|
|
54
|
+
If the user specifies an output directory, use that directory exactly.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
src/api/generated-types
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
api-contract generate <openapi-file> --output src/api/generated-types
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Do not substitute another directory unless the requested location is invalid or inaccessible.
|
|
69
|
+
|
|
70
|
+
### Default output
|
|
71
|
+
|
|
72
|
+
If the user does not specify an output directory, invoke the CLI without an output option:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
api-contract generate <openapi-file>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The CLI resolves the default output relative to the current working directory:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
api-contract/generated/
|
|
82
|
+
├── types.ts
|
|
83
|
+
└── api.ts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Workflow
|
|
89
|
+
|
|
90
|
+
### 1. Identify the OpenAPI file
|
|
91
|
+
|
|
92
|
+
Determine the OpenAPI specification from the user's request.
|
|
93
|
+
|
|
94
|
+
If the user attached a file, use that file.
|
|
95
|
+
|
|
96
|
+
If the user provides a repository path, use that path.
|
|
97
|
+
|
|
98
|
+
If multiple possible OpenAPI files exist and the intended file cannot be determined, ask the user rather than guessing.
|
|
99
|
+
|
|
100
|
+
### 2. Determine the output directory
|
|
101
|
+
|
|
102
|
+
Use the user-specified output directory when provided.
|
|
103
|
+
|
|
104
|
+
Otherwise let the CLI determine the default output location by omitting `--output`.
|
|
105
|
+
|
|
106
|
+
### 3. Generate
|
|
107
|
+
|
|
108
|
+
If the user specified an output directory, run:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
api-contract generate <openapi-file> --output <output-directory>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Otherwise run:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
api-contract generate <openapi-file>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Do not manually reproduce the generation logic.
|
|
121
|
+
|
|
122
|
+
### 4. Verify
|
|
123
|
+
|
|
124
|
+
After generation, verify that the expected files exist:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
types.ts
|
|
128
|
+
api.ts
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Inspect the generated output enough to confirm that it contains TypeScript generated from the supplied OpenAPI specification.
|
|
132
|
+
|
|
133
|
+
### 5. Handle existing output
|
|
134
|
+
|
|
135
|
+
If the output directory already contains generated files, the CLI may replace them.
|
|
136
|
+
|
|
137
|
+
Do not modify unrelated files.
|
|
138
|
+
|
|
139
|
+
Do not modify handwritten application code.
|
|
140
|
+
|
|
141
|
+
### 6. Report
|
|
142
|
+
|
|
143
|
+
Provide:
|
|
144
|
+
|
|
145
|
+
#### Generation Summary
|
|
146
|
+
|
|
147
|
+
* OpenAPI input
|
|
148
|
+
* output directory
|
|
149
|
+
* command executed
|
|
150
|
+
* generated files
|
|
151
|
+
|
|
152
|
+
#### Result
|
|
153
|
+
|
|
154
|
+
Confirm whether generation succeeded.
|
|
155
|
+
|
|
156
|
+
#### Generated Files
|
|
157
|
+
|
|
158
|
+
List the generated files using repository-relative paths where possible.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Important Rules
|
|
163
|
+
|
|
164
|
+
* Always use `api-contract generate`.
|
|
165
|
+
* Never manually generate the TypeScript output.
|
|
166
|
+
* Respect a user-specified output directory exactly.
|
|
167
|
+
* Let the CLI default to `api-contract/generated/` relative to the current working directory when no output directory is provided.
|
|
168
|
+
* Do not modify application code.
|
|
169
|
+
* Do not modify unrelated files.
|
|
170
|
+
* Do not introduce an LLM/API dependency into `api-contract`.
|
|
171
|
+
* If the OpenAPI input cannot be identified, ask the user.
|
|
172
|
+
* If the requested output location cannot be used, explain the problem instead of silently choosing another location.
|