@facetlayer/prism-framework 0.4.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/.claude/settings.local.json +20 -0
- package/CHANGELOG +28 -0
- package/CLAUDE.md +44 -0
- package/README.md +47 -0
- package/build.mts +8 -0
- package/dist/call-command.d.ts +13 -0
- package/dist/call-command.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +475 -0
- package/dist/config/ConfigFile.d.ts +7 -0
- package/dist/config/ConfigFile.d.ts.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/loadConfig.d.ts +11 -0
- package/dist/config/loadConfig.d.ts.map +1 -0
- package/dist/generate-api-clients.d.ts +6 -0
- package/dist/generate-api-clients.d.ts.map +1 -0
- package/dist/getPorts.d.ts +10 -0
- package/dist/getPorts.d.ts.map +1 -0
- package/dist/list-endpoints-command.d.ts +5 -0
- package/dist/list-endpoints-command.d.ts.map +1 -0
- package/dist/loadEnv.d.ts +12 -0
- package/dist/loadEnv.d.ts.map +1 -0
- package/docs/endpoint-tools.md +116 -0
- package/docs/env-files.md +64 -0
- package/docs/generate-api-clients-config.md +84 -0
- package/docs/getting-started.md +86 -0
- package/package.json +43 -0
- package/src/call-command.ts +147 -0
- package/src/cli.ts +163 -0
- package/src/config/ConfigFile.ts +7 -0
- package/src/config/index.ts +3 -0
- package/src/config/loadConfig.ts +58 -0
- package/src/generate-api-clients.ts +203 -0
- package/src/getPorts.ts +39 -0
- package/src/list-endpoints-command.ts +34 -0
- package/src/loadEnv.ts +59 -0
- package/test/call-command.test.ts +96 -0
- package/test/generate-api-clients.test.ts +33 -0
- package/test/generate-api-clients.test.ts.disabled +75 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(git mv:*)",
|
|
5
|
+
"Bash(prism-endpoint:*)",
|
|
6
|
+
"Bash(pnpm build:*)",
|
|
7
|
+
"Bash(pnpm install:*)",
|
|
8
|
+
"Bash(pnpm typecheck:*)",
|
|
9
|
+
"Skill(vibe-code-cleanup)",
|
|
10
|
+
"Bash(node build.mts:*)",
|
|
11
|
+
"Bash(pnpm test:*)",
|
|
12
|
+
"Bash(node dist/cli.js:*)",
|
|
13
|
+
"Bash(mkdir:*)",
|
|
14
|
+
"Bash(node /Users/andy/node-libraries/prism-framework-tools/dist/cli.js generate-api-clients:*)",
|
|
15
|
+
"Bash(pnpm local:install:*)"
|
|
16
|
+
],
|
|
17
|
+
"deny": [],
|
|
18
|
+
"ask": []
|
|
19
|
+
}
|
|
20
|
+
}
|
package/CHANGELOG
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
0.4.0
|
|
3
|
+
- Renamed from @facetlayer/prism-framework-tools to @facetlayer/prism-framework
|
|
4
|
+
|
|
5
|
+
0.3.0
|
|
6
|
+
- Add config file support (.prism.qc) with parent directory search
|
|
7
|
+
- Fix API client type generation issues
|
|
8
|
+
- Add generate-api-clients-config documentation
|
|
9
|
+
- Add endpoint-tools and env-files documentation
|
|
10
|
+
- Safety checks for Zod schemas incompatible with OpenAPI
|
|
11
|
+
- Improve getting-started docs
|
|
12
|
+
|
|
13
|
+
0.2.5
|
|
14
|
+
- Add list-docs and get-doc (with doc-files-helper)
|
|
15
|
+
|
|
16
|
+
0.2.4
|
|
17
|
+
- Handle JSONish params in 'call' command
|
|
18
|
+
- generate-api-clients now uses --out param
|
|
19
|
+
|
|
20
|
+
0.2.3
|
|
21
|
+
- Fix for list-endpoints
|
|
22
|
+
|
|
23
|
+
0.2.1
|
|
24
|
+
- Add list-endpoints command
|
|
25
|
+
- Call endpoint - don't fail on response schema failure
|
|
26
|
+
|
|
27
|
+
0.1.0
|
|
28
|
+
- initial version
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# prism-framework
|
|
2
|
+
|
|
3
|
+
Base library and CLI tools for the Prism app framework ecosystem.
|
|
4
|
+
|
|
5
|
+
## Important Files and Directories
|
|
6
|
+
|
|
7
|
+
### Source Code (`src/`)
|
|
8
|
+
- `cli.ts` - Main CLI entry point, uses yargs for argument parsing
|
|
9
|
+
- `call-command.ts` - Logic for calling endpoints, handles JSON parsing of arguments
|
|
10
|
+
- `generate-api-clients.ts` - Generates TypeScript types from OpenAPI schema
|
|
11
|
+
- `list-endpoints-command.ts` - Lists available endpoints from the API server
|
|
12
|
+
- `loadEnv.ts` - Environment variable loading and validation
|
|
13
|
+
- `getPorts.ts` - Port number utilities
|
|
14
|
+
|
|
15
|
+
### Documentation (`docs/`)
|
|
16
|
+
|
|
17
|
+
Markdown files for documentation.
|
|
18
|
+
|
|
19
|
+
Run `doc-files list-docs` to understand the format.
|
|
20
|
+
|
|
21
|
+
- `getting-started.md` - Setup guide for Prism Framework projects
|
|
22
|
+
- `run-endpoint-tool.md` - Detailed CLI usage documentation
|
|
23
|
+
- `env-files.md` - Environment configuration strategy
|
|
24
|
+
|
|
25
|
+
### Tests (`test/`)
|
|
26
|
+
- `call-command.test.ts` - Unit tests for argument parsing logic
|
|
27
|
+
|
|
28
|
+
### Build Output
|
|
29
|
+
- `dist/cli.js` - Compiled CLI executable (ES modules)
|
|
30
|
+
|
|
31
|
+
## Build Commands
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm build # Build the project
|
|
35
|
+
pnpm test # Run tests with Vitest
|
|
36
|
+
pnpm typecheck # TypeScript type checking
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Key Dependencies
|
|
40
|
+
|
|
41
|
+
- `yargs` - CLI argument parsing
|
|
42
|
+
- `dotenv` - Environment variable loading
|
|
43
|
+
- `@facetlayer/doc-files-helper` - Documentation file management
|
|
44
|
+
- `@facetlayer/prism-framework-api` - Prism API framework types
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @facetlayer/prism-framework
|
|
2
|
+
|
|
3
|
+
Base library and CLI tools for the Prism app framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @facetlayer/prism-framework
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI Commands
|
|
12
|
+
|
|
13
|
+
| Command | Description |
|
|
14
|
+
|---------|-------------|
|
|
15
|
+
| `prism list-endpoints` | List all available endpoints from the API server |
|
|
16
|
+
| `prism call [METHOD] [PATH] [--args]` | Call an endpoint on the running API server |
|
|
17
|
+
| `prism generate-api-clients --out <file>` | Generate TypeScript types from OpenAPI schema |
|
|
18
|
+
| `prism list-docs` | List available documentation |
|
|
19
|
+
| `prism get-doc <name>` | Display a specific documentation file |
|
|
20
|
+
|
|
21
|
+
### Examples
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# List all endpoints
|
|
25
|
+
prism list-endpoints
|
|
26
|
+
|
|
27
|
+
# Call endpoints
|
|
28
|
+
prism call /api/users # GET request
|
|
29
|
+
prism call POST /api/users --name "John" # POST with body
|
|
30
|
+
prism call POST /api/data --config '{"timeout":30}' # JSON arguments
|
|
31
|
+
|
|
32
|
+
# Generate TypeScript types
|
|
33
|
+
prism generate-api-clients --out ./src/api-types.ts
|
|
34
|
+
|
|
35
|
+
# Access documentation
|
|
36
|
+
prism list-docs
|
|
37
|
+
prism get-doc getting-started
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
Once installed, the CLI has `prism list-docs` and `prism get-doc ...` commands to browse through the documentation files.
|
|
43
|
+
|
|
44
|
+
Run `prism list-docs` to see available documentation topics, including:
|
|
45
|
+
- `getting-started` - Setup guide for Prism Framework projects
|
|
46
|
+
- `run-endpoint-tool` - Detailed CLI usage documentation
|
|
47
|
+
- `env-files` - Environment configuration strategy
|
package/build.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export interface CallEndpointLooseOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
positionalArgs: string[];
|
|
5
|
+
namedArgs: Record<string, any>;
|
|
6
|
+
quiet?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseNamedArgs(namedArgs: Record<string, any>): Record<string, any>;
|
|
9
|
+
/**
|
|
10
|
+
* Make an HTTP request to the local Prism API server
|
|
11
|
+
*/
|
|
12
|
+
export declare function callEndpoint(looseOptions: CallEndpointLooseOptions): Promise<any>;
|
|
13
|
+
//# sourceMappingURL=call-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-command.d.ts","sourceRoot":"","sources":["../src/call-command.ts"],"names":[],"mappings":";AAIA,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAC,GAAG,CAAC,CAAA;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAmCD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAMlF;AAkCD;;GAEG;AACH,wBAAsB,YAAY,CAAC,YAAY,EAAE,wBAAwB,gBA2DxE"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { hideBin } from "yargs/helpers";
|
|
6
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
7
|
+
import { join as join2, dirname as dirname3 } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
// src/loadEnv.ts
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import { config } from "dotenv";
|
|
14
|
+
function loadEnv(cwd) {
|
|
15
|
+
const envPath = path.resolve(cwd, ".env");
|
|
16
|
+
if (!fs.existsSync(envPath)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`No .env file found at ${envPath}
|
|
19
|
+
|
|
20
|
+
Please create a .env file with PRISM_API_PORT defined.
|
|
21
|
+
Example:
|
|
22
|
+
PRISM_API_PORT=3000`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const result = config({ path: envPath });
|
|
26
|
+
if (result.error) {
|
|
27
|
+
throw new Error(`Failed to load .env file: ${result.error.message}`);
|
|
28
|
+
}
|
|
29
|
+
const port = process.env.PRISM_API_PORT;
|
|
30
|
+
if (!port) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"PRISM_API_PORT is not defined in .env file\n\nPlease add PRISM_API_PORT to your .env file.\nExample:\n PRISM_API_PORT=3000"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const portNumber = parseInt(port, 10);
|
|
36
|
+
if (isNaN(portNumber) || portNumber <= 0 || portNumber > 65535) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Invalid PRISM_API_PORT value: ${port}
|
|
39
|
+
|
|
40
|
+
Port must be a number between 1 and 65535`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
port: portNumber,
|
|
45
|
+
baseUrl: `http://localhost:${portNumber}`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/call-command.ts
|
|
50
|
+
var EVERY_METHOD = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
51
|
+
function parseValue(value) {
|
|
52
|
+
if (typeof value === "string") {
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(trimmed);
|
|
57
|
+
} catch {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
62
|
+
const result = {};
|
|
63
|
+
for (const [k, v] of Object.entries(value)) {
|
|
64
|
+
result[k] = parseValue(v);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function parseNamedArgs(namedArgs) {
|
|
71
|
+
const result = {};
|
|
72
|
+
for (const [key, value] of Object.entries(namedArgs)) {
|
|
73
|
+
result[key] = parseValue(value);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
function parseOptions(looseOptions) {
|
|
78
|
+
const result = {
|
|
79
|
+
baseUrl: looseOptions.baseUrl,
|
|
80
|
+
method: "GET",
|
|
81
|
+
path: "/",
|
|
82
|
+
requestBody: parseNamedArgs(looseOptions.namedArgs)
|
|
83
|
+
};
|
|
84
|
+
for (const positional of looseOptions.positionalArgs) {
|
|
85
|
+
if (positional.startsWith("/")) {
|
|
86
|
+
result.path = positional;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (EVERY_METHOD.has(positional.toUpperCase())) {
|
|
90
|
+
result.method = positional.toUpperCase();
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (positional.startsWith("http:") || positional.startsWith("https:")) {
|
|
94
|
+
result.baseUrl = positional;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
throw new Error("unrecognized positional arg:" + positional);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
async function callEndpoint(looseOptions) {
|
|
102
|
+
const options = parseOptions(looseOptions);
|
|
103
|
+
const url = `${options.baseUrl}${options.path}`;
|
|
104
|
+
const requestOptions = {
|
|
105
|
+
method: options.method,
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json"
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
if (["POST", "PUT", "PATCH", "DELETE"].includes(options.method) && options.requestBody && Object.keys(options.requestBody).length > 0) {
|
|
111
|
+
requestOptions.body = JSON.stringify(options.requestBody);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(url, requestOptions);
|
|
115
|
+
if (!looseOptions.quiet)
|
|
116
|
+
console.log("Response status: " + response.status);
|
|
117
|
+
const responseText = await response.text();
|
|
118
|
+
if (!looseOptions.quiet)
|
|
119
|
+
console.log("Response: ", responseText);
|
|
120
|
+
let responseData;
|
|
121
|
+
try {
|
|
122
|
+
responseData = responseText ? JSON.parse(responseText) : null;
|
|
123
|
+
} catch {
|
|
124
|
+
responseData = responseText;
|
|
125
|
+
}
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`HTTP ${response.status} ${response.statusText}
|
|
129
|
+
Response: ${JSON.stringify(responseData, null, 2)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return responseData;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof Error) {
|
|
135
|
+
if (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED")) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Failed to connect to ${url}
|
|
138
|
+
|
|
139
|
+
Make sure your Prism API server is running.
|
|
140
|
+
The server should be listening on the port specified in .env (PRISM_API_PORT)`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/list-endpoints-command.ts
|
|
149
|
+
async function listEndpoints(baseUrl) {
|
|
150
|
+
try {
|
|
151
|
+
const response = await callEndpoint({
|
|
152
|
+
baseUrl,
|
|
153
|
+
positionalArgs: ["GET", "/endpoints.json"],
|
|
154
|
+
namedArgs: {},
|
|
155
|
+
quiet: true
|
|
156
|
+
});
|
|
157
|
+
const endpoints = response.endpoints;
|
|
158
|
+
console.log("Available endpoints:\n");
|
|
159
|
+
if (Array.isArray(endpoints)) {
|
|
160
|
+
for (const endpoint of endpoints) {
|
|
161
|
+
const fullPath = `${endpoint.method} ${endpoint.path}`;
|
|
162
|
+
console.log(` ${fullPath}`);
|
|
163
|
+
if (endpoint.description) {
|
|
164
|
+
console.log(` ${endpoint.description}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
console.log(JSON.stringify(endpoints, null, 2));
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error("Could not list endpoints. The server may not support the /api/endpoints introspection endpoint.");
|
|
172
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/generate-api-clients.ts
|
|
178
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
179
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
180
|
+
function capitalizeFirst(str) {
|
|
181
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
182
|
+
}
|
|
183
|
+
function convertToExpressPath(openApiPath) {
|
|
184
|
+
return openApiPath.replace(/\{([^}]+)\}/g, ":$1");
|
|
185
|
+
}
|
|
186
|
+
function schemaToTypeScript(schema, components, indent = 0) {
|
|
187
|
+
if (!schema) {
|
|
188
|
+
return "unknown";
|
|
189
|
+
}
|
|
190
|
+
const indentStr = " ".repeat(indent);
|
|
191
|
+
if (schema.$ref) {
|
|
192
|
+
const refName = schema.$ref.split("/").pop();
|
|
193
|
+
return refName;
|
|
194
|
+
}
|
|
195
|
+
if (schema.type === "array") {
|
|
196
|
+
const itemType = schemaToTypeScript(schema.items, components, indent);
|
|
197
|
+
return `Array<${itemType}>`;
|
|
198
|
+
}
|
|
199
|
+
if (schema.type === "object" || schema.properties) {
|
|
200
|
+
const properties = schema.properties || {};
|
|
201
|
+
const required = schema.required || [];
|
|
202
|
+
if (Object.keys(properties).length === 0) {
|
|
203
|
+
return "Record<string, unknown>";
|
|
204
|
+
}
|
|
205
|
+
const props = Object.entries(properties).map(([key, value]) => {
|
|
206
|
+
const isRequired = required.includes(key);
|
|
207
|
+
const propType = schemaToTypeScript(value, components, indent + 1);
|
|
208
|
+
const optional = isRequired ? "" : "?";
|
|
209
|
+
return `${indentStr} ${key}${optional}: ${propType};`;
|
|
210
|
+
});
|
|
211
|
+
return `{
|
|
212
|
+
${props.join("\n")}
|
|
213
|
+
${indentStr}}`;
|
|
214
|
+
}
|
|
215
|
+
switch (schema.type) {
|
|
216
|
+
case "string":
|
|
217
|
+
return "string";
|
|
218
|
+
case "number":
|
|
219
|
+
case "integer":
|
|
220
|
+
return "number";
|
|
221
|
+
case "boolean":
|
|
222
|
+
return "boolean";
|
|
223
|
+
case "null":
|
|
224
|
+
return "null";
|
|
225
|
+
default:
|
|
226
|
+
return "unknown";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function generateApiClients(baseUrl, outputFiles) {
|
|
230
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
231
|
+
if (outputFiles.length === 0) {
|
|
232
|
+
throw new Error("At least one --out file must be specified");
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const response = await fetch(`${baseUrl}/openapi.json`);
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`Failed to fetch OpenAPI schema: ${response.status} ${response.statusText}
|
|
239
|
+
Make sure the Prism API server is running at ${baseUrl}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const schema = await response.json();
|
|
243
|
+
const lines = [];
|
|
244
|
+
const endpointMap = [];
|
|
245
|
+
if ((_a = schema.components) == null ? void 0 : _a.schemas) {
|
|
246
|
+
lines.push("// Component Schemas");
|
|
247
|
+
for (const [name, componentSchema] of Object.entries(schema.components.schemas)) {
|
|
248
|
+
const typeStr = schemaToTypeScript(componentSchema, schema.components.schemas);
|
|
249
|
+
lines.push(`export type ${name} = ${typeStr};
|
|
250
|
+
`);
|
|
251
|
+
}
|
|
252
|
+
lines.push("");
|
|
253
|
+
}
|
|
254
|
+
lines.push("// Endpoint Types");
|
|
255
|
+
for (const [pathStr, pathItem] of Object.entries(schema.paths)) {
|
|
256
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
257
|
+
if (!operation.operationId) continue;
|
|
258
|
+
const typeName = capitalizeFirst(operation.operationId);
|
|
259
|
+
const expressPath = convertToExpressPath(pathStr);
|
|
260
|
+
endpointMap.push({ method: method.toLowerCase(), path: expressPath, operationId: operation.operationId });
|
|
261
|
+
const requestSchema = (_d = (_c = (_b = operation.requestBody) == null ? void 0 : _b.content) == null ? void 0 : _c["application/json"]) == null ? void 0 : _d.schema;
|
|
262
|
+
const requestType = requestSchema ? schemaToTypeScript(requestSchema, (_e = schema.components) == null ? void 0 : _e.schemas) : "void";
|
|
263
|
+
lines.push(`export type ${typeName}Request = ${requestType};
|
|
264
|
+
`);
|
|
265
|
+
const responseSchema = (_i = (_h = (_g = (_f = operation.responses) == null ? void 0 : _f["200"]) == null ? void 0 : _g.content) == null ? void 0 : _h["application/json"]) == null ? void 0 : _i.schema;
|
|
266
|
+
const responseType = responseSchema ? schemaToTypeScript(responseSchema, (_j = schema.components) == null ? void 0 : _j.schemas) : "void";
|
|
267
|
+
lines.push(`export type ${typeName}Response = ${responseType};
|
|
268
|
+
`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
lines.push("// Union type of all valid endpoints");
|
|
272
|
+
lines.push("export type ApiEndpoint =");
|
|
273
|
+
const endpointStrings = endpointMap.map(({ method, path: path2 }) => ` | "${method} ${path2}"`);
|
|
274
|
+
lines.push(endpointStrings.join("\n"));
|
|
275
|
+
lines.push(";\n");
|
|
276
|
+
lines.push("// Generic Request/Response Types by Endpoint");
|
|
277
|
+
lines.push("export type RequestType<T extends ApiEndpoint> =");
|
|
278
|
+
const requestCases = endpointMap.map(
|
|
279
|
+
({ method, path: path2, operationId }) => ` T extends "${method} ${path2}" ? ${capitalizeFirst(operationId)}Request :`
|
|
280
|
+
);
|
|
281
|
+
lines.push(requestCases.join("\n"));
|
|
282
|
+
lines.push(" never;\n");
|
|
283
|
+
lines.push("export type ResponseType<T extends ApiEndpoint> =");
|
|
284
|
+
const responseCases = endpointMap.map(
|
|
285
|
+
({ method, path: path2, operationId }) => ` T extends "${method} ${path2}" ? ${capitalizeFirst(operationId)}Response :`
|
|
286
|
+
);
|
|
287
|
+
lines.push(responseCases.join("\n"));
|
|
288
|
+
lines.push(" never;\n");
|
|
289
|
+
const output = lines.join("\n");
|
|
290
|
+
const header = `// Generated API client types
|
|
291
|
+
// Auto-generated from OpenAPI schema - do not edit manually
|
|
292
|
+
|
|
293
|
+
`;
|
|
294
|
+
const content = header + output;
|
|
295
|
+
for (const outputFile of outputFiles) {
|
|
296
|
+
const resolvedPath = resolve2(outputFile);
|
|
297
|
+
mkdirSync(dirname(resolvedPath), { recursive: true });
|
|
298
|
+
writeFileSync(resolvedPath, content, "utf-8");
|
|
299
|
+
console.log(`Written: ${resolvedPath}`);
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error("\u274C Error generating client:", error);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/config/loadConfig.ts
|
|
308
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
309
|
+
import { dirname as dirname2, join, resolve as resolve3 } from "path";
|
|
310
|
+
import { parseFile } from "@facetlayer/qc";
|
|
311
|
+
var CONFIG_FILENAME = ".prism.qc";
|
|
312
|
+
function loadConfig(cwd) {
|
|
313
|
+
let currentDir = resolve3(cwd);
|
|
314
|
+
while (true) {
|
|
315
|
+
const configPath = join(currentDir, CONFIG_FILENAME);
|
|
316
|
+
if (existsSync2(configPath)) {
|
|
317
|
+
const config2 = parseConfigFile(configPath);
|
|
318
|
+
return { config: config2, configDir: currentDir };
|
|
319
|
+
}
|
|
320
|
+
const parentDir = dirname2(currentDir);
|
|
321
|
+
if (parentDir === currentDir) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
currentDir = parentDir;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function parseConfigFile(configPath) {
|
|
328
|
+
const content = readFileSync(configPath, "utf-8");
|
|
329
|
+
const queries = parseFile(content);
|
|
330
|
+
const generateApiClientTargets = [];
|
|
331
|
+
for (const query of queries) {
|
|
332
|
+
switch (query.command) {
|
|
333
|
+
case "generate-api-client": {
|
|
334
|
+
const outputFile = query.getStringValue("output-file");
|
|
335
|
+
generateApiClientTargets.push({ outputFile });
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
default:
|
|
339
|
+
throw new Error(`Unknown command "${query.command}" in ${CONFIG_FILENAME}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
generateApiClientTargets
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/cli.ts
|
|
348
|
+
import { DocFilesHelper } from "@facetlayer/doc-files-helper";
|
|
349
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
350
|
+
var __dirname = dirname3(__filename);
|
|
351
|
+
var __packageRoot = join2(__dirname, "..");
|
|
352
|
+
var packageJson = JSON.parse(
|
|
353
|
+
readFileSync2(join2(__packageRoot, "package.json"), "utf-8")
|
|
354
|
+
);
|
|
355
|
+
var docFiles = new DocFilesHelper({
|
|
356
|
+
dirs: [join2(__packageRoot, "docs")],
|
|
357
|
+
files: [join2(__packageRoot, "README.md")]
|
|
358
|
+
});
|
|
359
|
+
async function main() {
|
|
360
|
+
const args = yargs(hideBin(process.argv)).command(
|
|
361
|
+
"list-endpoints",
|
|
362
|
+
"List all available endpoints",
|
|
363
|
+
{},
|
|
364
|
+
async () => {
|
|
365
|
+
try {
|
|
366
|
+
const cwd = process.cwd();
|
|
367
|
+
const config2 = loadEnv(cwd);
|
|
368
|
+
console.log(`Using API server at: ${config2.baseUrl}
|
|
369
|
+
`);
|
|
370
|
+
await listEndpoints(config2.baseUrl);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error instanceof Error && error.stack) {
|
|
373
|
+
console.error(error.stack);
|
|
374
|
+
} else {
|
|
375
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
376
|
+
}
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
).command(
|
|
381
|
+
"call <positionals...>",
|
|
382
|
+
"Call an endpoint. Named args starting with { } or [ ] are parsed as JSON.",
|
|
383
|
+
(yargs2) => {
|
|
384
|
+
return yargs2;
|
|
385
|
+
},
|
|
386
|
+
async (argv) => {
|
|
387
|
+
try {
|
|
388
|
+
const cwd = process.cwd();
|
|
389
|
+
const config2 = loadEnv(cwd);
|
|
390
|
+
const requestData = {};
|
|
391
|
+
for (const [key, value] of Object.entries(argv)) {
|
|
392
|
+
if (["positionals", "_", "$0"].includes(key)) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
requestData[key] = value;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const result = await callEndpoint({
|
|
399
|
+
baseUrl: config2.baseUrl,
|
|
400
|
+
positionalArgs: argv.positionals,
|
|
401
|
+
namedArgs: requestData
|
|
402
|
+
});
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error("Error calling endpoint:");
|
|
405
|
+
if (error instanceof Error) {
|
|
406
|
+
console.error(error.message);
|
|
407
|
+
if (error.stack) {
|
|
408
|
+
console.error("\nStack trace:");
|
|
409
|
+
console.error(error.stack);
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
console.error(String(error));
|
|
413
|
+
}
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (error instanceof Error && error.stack) {
|
|
418
|
+
console.error(error.stack);
|
|
419
|
+
} else {
|
|
420
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
421
|
+
}
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
).command(
|
|
426
|
+
"generate-api-clients",
|
|
427
|
+
"Generate TypeScript API client types from OpenAPI schema",
|
|
428
|
+
(yargs2) => {
|
|
429
|
+
return yargs2.option("out", {
|
|
430
|
+
type: "string",
|
|
431
|
+
array: true,
|
|
432
|
+
describe: "Output file path(s) to write generated types to"
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
async (argv) => {
|
|
436
|
+
try {
|
|
437
|
+
const cwd = process.cwd();
|
|
438
|
+
const envConfig = loadEnv(cwd);
|
|
439
|
+
console.log(`Using API server at: ${envConfig.baseUrl}
|
|
440
|
+
`);
|
|
441
|
+
let outputFiles = [];
|
|
442
|
+
if (argv.out && argv.out.length > 0) {
|
|
443
|
+
outputFiles = argv.out;
|
|
444
|
+
} else {
|
|
445
|
+
const result = loadConfig(cwd);
|
|
446
|
+
if (!result || result.config.generateApiClientTargets.length === 0) {
|
|
447
|
+
console.error("Error: No output files specified.");
|
|
448
|
+
console.error("Either use --out to specify output files, or configure targets in .prism.qc:");
|
|
449
|
+
console.error(" generate-api-client-target out=./src/api-types.ts");
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
outputFiles = result.config.generateApiClientTargets.map((t) => t.outputFile);
|
|
453
|
+
}
|
|
454
|
+
await generateApiClients(envConfig.baseUrl, outputFiles);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
if (error instanceof Error && error.stack) {
|
|
457
|
+
console.error(error.stack);
|
|
458
|
+
} else {
|
|
459
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
460
|
+
}
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
docFiles.yargsSetup(args);
|
|
466
|
+
args.strictCommands().demandCommand(1, "You must specify a command").help().alias("help", "h").version(packageJson.version).alias("version", "v").example([
|
|
467
|
+
["$0 list-endpoints", "List all available endpoints"],
|
|
468
|
+
["$0 call /api/users", "Call GET /api/users"],
|
|
469
|
+
['$0 call POST /api/users --name "John" --email "john@example.com"', "call POST with data"],
|
|
470
|
+
[`$0 call POST /api/users --config '{"timeout":30}'`, "pass JSON objects as args"],
|
|
471
|
+
["$0 generate-api-clients --out ./api-types.ts", "Generate API client types to a file"],
|
|
472
|
+
["$0 generate-api-clients --out ./types.ts --out ./backup/types.ts", "Write to multiple files"]
|
|
473
|
+
]).parse();
|
|
474
|
+
}
|
|
475
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigFile.d.ts","sourceRoot":"","sources":["../../src/config/ConfigFile.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,wBAAwB,EAAE,uBAAuB,EAAE,CAAC;CACrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConfigFile } from './ConfigFile.ts';
|
|
2
|
+
export interface LoadConfigResult {
|
|
3
|
+
config: ConfigFile;
|
|
4
|
+
configDir: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Find and load the .prism.qc config file.
|
|
8
|
+
* Searches in the provided directory and parent directories until found or root is reached.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadConfig(cwd: string): LoadConfigResult | null;
|
|
11
|
+
//# sourceMappingURL=loadConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadConfig.d.ts","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AAI3E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAkB/D"}
|