@aloma.io/integration-sdk 3.8.50 → 3.8.52
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/OPENAPI_TO_CONNECTOR.md +216 -0
- package/README.md +15 -1
- package/build/cli.mjs +57 -1
- package/build/internal/fetcher/fetcher.d.mts +1 -0
- package/build/openapi-to-connector.d.mts +35 -0
- package/build/openapi-to-connector.mjs +252 -0
- package/examples/generate-connector.sh +35 -0
- package/examples/generated-controller.mts +81 -0
- package/examples/sample-api.json +325 -0
- package/examples/sample-api.yaml +222 -0
- package/examples/simple-api.json +39 -0
- package/package.json +11 -4
- package/src/builder/index.mts +3 -3
- package/src/builder/runtime-context.mts +2 -2
- package/src/cli.mts +65 -1
- package/src/controller/index.mts +2 -2
- package/src/internal/connector/server/on-connect/index.mts +9 -9
- package/src/internal/fetcher/fetcher.mts +5 -5
- package/src/internal/websocket/transport/index.mjs +4 -5
- package/src/openapi-to-connector.mts +305 -0
- package/test/openapi-to-connector.test.mts +207 -0
package/src/builder/index.mts
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
import 'dotenv/config';
|
2
2
|
import fs from 'node:fs';
|
3
3
|
import path from 'node:path';
|
4
|
-
import {
|
5
|
-
import {
|
4
|
+
import {fileURLToPath} from 'node:url';
|
5
|
+
import {notEmpty} from '../internal/util/index.mjs';
|
6
6
|
import RuntimeContext from './runtime-context.mjs';
|
7
7
|
|
8
8
|
const DIR_OFFSET = '/../../../../../';
|
@@ -33,7 +33,7 @@ export type ConfigField =
|
|
33
33
|
/**
|
34
34
|
* minimum height of the field
|
35
35
|
*/
|
36
|
-
height?: number
|
36
|
+
height?: number;
|
37
37
|
/**
|
38
38
|
* the type of the field
|
39
39
|
*/
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import fs from 'node:fs';
|
2
|
-
import {
|
3
|
-
import {
|
2
|
+
import {AbstractController} from '../controller/index.mjs';
|
3
|
+
import {Connector} from '../internal/index.mjs';
|
4
4
|
|
5
5
|
/**
|
6
6
|
* Runtime context to manage the lifecycle of the connector
|
package/src/cli.mts
CHANGED
@@ -10,6 +10,7 @@ import {TARGET_DIR} from './builder/index.mjs';
|
|
10
10
|
import {notEmpty} from './internal/util/index.mjs';
|
11
11
|
import JWE from './internal/util/jwe/index.mjs';
|
12
12
|
import parseTypes from './transform/index.mjs';
|
13
|
+
import {OpenAPIToConnector} from './openapi-to-connector.mjs';
|
13
14
|
|
14
15
|
const exec = util.promisify(ChildProcess.exec);
|
15
16
|
|
@@ -101,7 +102,7 @@ program
|
|
101
102
|
await generateKeys({target});
|
102
103
|
|
103
104
|
console.log('Installing dependencies ...');
|
104
|
-
await exec(`cd ${target}; yarn`);
|
105
|
+
await exec(`cd ${target}; yarn --ignore-engines`);
|
105
106
|
|
106
107
|
console.log('Building ...');
|
107
108
|
await exec(`cd ${target}; yarn build`);
|
@@ -131,6 +132,69 @@ program
|
|
131
132
|
new Extractor().extract('./src/controller/index.mts', './build/.controller.json');
|
132
133
|
});
|
133
134
|
|
135
|
+
program
|
136
|
+
.command('from-openapi')
|
137
|
+
.description('Generate a connector controller from an OpenAPI specification')
|
138
|
+
.argument('<name>', 'name of the connector project')
|
139
|
+
.requiredOption('--connector-id <id>', 'id of the connector')
|
140
|
+
.requiredOption('--spec <file>', 'OpenAPI specification file (JSON or YAML)')
|
141
|
+
.option('--out <file>', 'output file path for the controller', 'src/controller/index.mts')
|
142
|
+
.action(async (name, options) => {
|
143
|
+
name = name.replace(/[\/\.]/gi, '');
|
144
|
+
if (!name) throw new Error('name is empty');
|
145
|
+
|
146
|
+
const target = `${process.cwd()}/${name}`;
|
147
|
+
|
148
|
+
try {
|
149
|
+
// Read and parse the OpenAPI spec
|
150
|
+
const specContent = fs.readFileSync(options.spec, 'utf-8');
|
151
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
152
|
+
|
153
|
+
// Create the connector project structure
|
154
|
+
fs.mkdirSync(target);
|
155
|
+
console.log('Creating connector project...');
|
156
|
+
extract({...options, target, name});
|
157
|
+
|
158
|
+
// Generate the controller from OpenAPI spec
|
159
|
+
console.log('Generating controller from OpenAPI specification...');
|
160
|
+
const generator = new OpenAPIToConnector(spec, name);
|
161
|
+
const controllerCode = generator.generateController();
|
162
|
+
|
163
|
+
// Write the generated controller
|
164
|
+
const controllerPath = `${target}/${options.out}`;
|
165
|
+
fs.mkdirSync(path.dirname(controllerPath), {recursive: true});
|
166
|
+
fs.writeFileSync(controllerPath, controllerCode);
|
167
|
+
|
168
|
+
console.log('Generating keys...');
|
169
|
+
await generateKeys({target});
|
170
|
+
|
171
|
+
console.log('Installing dependencies...');
|
172
|
+
await exec(`cd ${target}; yarn --ignore-engines`);
|
173
|
+
|
174
|
+
console.log('Building...');
|
175
|
+
await exec(`cd ${target}; yarn build`);
|
176
|
+
|
177
|
+
console.log(`
|
178
|
+
✅ Success! Generated connector from OpenAPI specification
|
179
|
+
📝 Connector name: ${name}
|
180
|
+
📊 Found ${generator.getOperationsCount()} operations
|
181
|
+
📄 Controller generated: ${options.out}
|
182
|
+
|
183
|
+
Next steps:
|
184
|
+
1.) Add the connector to a workspace
|
185
|
+
2.) Edit ./${name}/.env and insert the registration token
|
186
|
+
3.) Implement the actual API calls in each method in ${options.out}
|
187
|
+
4.) Start the connector with cd ./${name}/; yarn start`);
|
188
|
+
} catch (error) {
|
189
|
+
console.error('❌ Error:', error instanceof Error ? error.message : 'Unknown error');
|
190
|
+
// Clean up on error
|
191
|
+
if (fs.existsSync(target)) {
|
192
|
+
fs.rmSync(target, {recursive: true, force: true});
|
193
|
+
}
|
194
|
+
process.exit(1);
|
195
|
+
}
|
196
|
+
});
|
197
|
+
|
134
198
|
class Extractor {
|
135
199
|
async extract(source, target) {
|
136
200
|
notEmpty(source, 'source');
|
package/src/controller/index.mts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import {
|
1
|
+
import {ConfigField} from '../index.mjs';
|
2
2
|
import Fetcher from '../internal/fetcher/fetcher.mjs';
|
3
|
-
import {
|
3
|
+
import {OAuth} from '../internal/fetcher/oauth-fetcher.mjs';
|
4
4
|
|
5
5
|
/**
|
6
6
|
* Abstract controller class
|
@@ -1,12 +1,12 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
1
|
+
import {init} from '@paralleldrive/cuid2';
|
2
|
+
import {AbstractController} from '../../../../index.mjs';
|
3
3
|
import Dispatcher from '../../../dispatcher/index.mjs';
|
4
4
|
import Fetcher from '../../../fetcher/fetcher.mjs';
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import {
|
8
|
-
import {
|
9
|
-
import {
|
5
|
+
import {Config} from '../../../websocket/config.mjs';
|
6
|
+
import {decryptConfig} from './decrypt-config.mjs';
|
7
|
+
import {patchFinishOAuth} from './finish-oauth.mjs';
|
8
|
+
import {makeOAuth} from './make-oauth.mjs';
|
9
|
+
import {patchStartOAuth} from './start-oauth.mjs';
|
10
10
|
|
11
11
|
const cuid = init({length: 32});
|
12
12
|
|
@@ -28,9 +28,9 @@ export const onConnect = ({
|
|
28
28
|
secrets,
|
29
29
|
config,
|
30
30
|
});
|
31
|
-
|
31
|
+
|
32
32
|
if (decrypted?.oauthResult?.scope) {
|
33
|
-
console.log(`Scope: ${decrypted.oauthResult.scope}`)
|
33
|
+
console.log(`Scope: ${decrypted.oauthResult.scope}`);
|
34
34
|
}
|
35
35
|
|
36
36
|
await patchStartOAuth({dispatcher, decrypted});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import {unwrap} from '../util/index.mjs';
|
2
2
|
|
3
3
|
/**
|
4
4
|
* http request fetcher
|
@@ -17,7 +17,7 @@ export default class Fetcher {
|
|
17
17
|
retry?: number;
|
18
18
|
baseUrl?: string;
|
19
19
|
onResponse?: (response: Response) => void;
|
20
|
-
customize?: (request: {
|
20
|
+
customize?: (request: {[key: string]: any}) => void;
|
21
21
|
} = {}) {
|
22
22
|
this.retry = retry;
|
23
23
|
this.baseUrl = baseUrl;
|
@@ -68,7 +68,7 @@ export default class Fetcher {
|
|
68
68
|
/**
|
69
69
|
* request headers like Accept, Content-type
|
70
70
|
*/
|
71
|
-
headers?: {
|
71
|
+
headers?: {[key: string]: any};
|
72
72
|
/**
|
73
73
|
* request body like "hello world" or {hello: "world"}
|
74
74
|
*/
|
@@ -81,7 +81,7 @@ export default class Fetcher {
|
|
81
81
|
baseUrl = local.baseUrl;
|
82
82
|
options ||= {};
|
83
83
|
|
84
|
-
const options0: any = {
|
84
|
+
const options0: any = {...options};
|
85
85
|
|
86
86
|
if (retries == null) retries = local.retry;
|
87
87
|
|
@@ -139,7 +139,7 @@ export default class Fetcher {
|
|
139
139
|
}
|
140
140
|
|
141
141
|
if (status === 204) {
|
142
|
-
return {
|
142
|
+
return {ok: true};
|
143
143
|
}
|
144
144
|
|
145
145
|
return unwrap(ret, options0);
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import {
|
1
|
+
import {init} from '@paralleldrive/cuid2';
|
2
2
|
import C from '../connection/constants.mjs';
|
3
3
|
const cuid = init({length: 32});
|
4
4
|
|
5
5
|
import WebSocket from 'ws';
|
6
|
-
import {
|
7
|
-
import {
|
6
|
+
import {DurableWebsocket} from './durable.mjs';
|
7
|
+
import {Callback, Packet} from './packet.mjs';
|
8
8
|
|
9
9
|
const cleanInterval = 45 * 1000;
|
10
10
|
const pingInterval = 30 * 1000;
|
@@ -191,5 +191,4 @@ class Transport {
|
|
191
191
|
}
|
192
192
|
}
|
193
193
|
|
194
|
-
export {
|
195
|
-
|
194
|
+
export {Transport};
|
@@ -0,0 +1,305 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import {Command} from 'commander';
|
4
|
+
import fs from 'node:fs';
|
5
|
+
import path from 'node:path';
|
6
|
+
import yaml from 'js-yaml';
|
7
|
+
import {OpenAPIV3} from 'openapi-types';
|
8
|
+
import {z} from 'zod';
|
9
|
+
|
10
|
+
// OpenAPI 3.x validation schema
|
11
|
+
const OpenAPISchema = z.object({
|
12
|
+
openapi: z.string().regex(/^3\.\d+\.\d+$/),
|
13
|
+
info: z.object({
|
14
|
+
title: z.string(),
|
15
|
+
version: z.string(),
|
16
|
+
description: z.string().optional(),
|
17
|
+
}),
|
18
|
+
paths: z.record(z.string(), z.any()),
|
19
|
+
servers: z
|
20
|
+
.array(
|
21
|
+
z.object({
|
22
|
+
url: z.string(),
|
23
|
+
description: z.string().optional(),
|
24
|
+
})
|
25
|
+
)
|
26
|
+
.optional(),
|
27
|
+
components: z.any().optional(),
|
28
|
+
});
|
29
|
+
|
30
|
+
interface OperationInfo {
|
31
|
+
method: string;
|
32
|
+
path: string;
|
33
|
+
operationId?: string;
|
34
|
+
summary?: string;
|
35
|
+
description?: string;
|
36
|
+
parameters?: any[];
|
37
|
+
requestBody?: any;
|
38
|
+
responses?: any;
|
39
|
+
}
|
40
|
+
|
41
|
+
export class OpenAPIToConnector {
|
42
|
+
private spec: OpenAPIV3.Document;
|
43
|
+
private connectorName: string;
|
44
|
+
|
45
|
+
constructor(spec: OpenAPIV3.Document, connectorName: string) {
|
46
|
+
this.spec = spec;
|
47
|
+
this.connectorName = connectorName;
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Parse OpenAPI spec from JSON or YAML string
|
52
|
+
*/
|
53
|
+
static parseSpec(specString: string): OpenAPIV3.Document {
|
54
|
+
let parsed: any;
|
55
|
+
|
56
|
+
try {
|
57
|
+
// Try parsing as JSON first
|
58
|
+
parsed = JSON.parse(specString);
|
59
|
+
} catch {
|
60
|
+
try {
|
61
|
+
// If JSON fails, try YAML
|
62
|
+
parsed = yaml.load(specString);
|
63
|
+
} catch (error) {
|
64
|
+
throw new Error(`Failed to parse OpenAPI spec: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
// Validate against OpenAPI 3.x schema
|
69
|
+
const validationResult = OpenAPISchema.safeParse(parsed);
|
70
|
+
if (!validationResult.success) {
|
71
|
+
const errors = validationResult.error.errors.map((err) => `${err.path.join('.')}: ${err.message}`).join(', ');
|
72
|
+
throw new Error(`Invalid OpenAPI 3.x specification: ${errors}`);
|
73
|
+
}
|
74
|
+
|
75
|
+
return parsed as OpenAPIV3.Document;
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Extract all operations from the OpenAPI spec
|
80
|
+
*/
|
81
|
+
private extractOperations(): OperationInfo[] {
|
82
|
+
const operations: OperationInfo[] = [];
|
83
|
+
|
84
|
+
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
|
85
|
+
if (!pathItem) continue;
|
86
|
+
|
87
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'] as const;
|
88
|
+
|
89
|
+
for (const method of methods) {
|
90
|
+
const operation = pathItem[method];
|
91
|
+
if (!operation) continue;
|
92
|
+
|
93
|
+
operations.push({
|
94
|
+
method: method.toUpperCase(),
|
95
|
+
path,
|
96
|
+
operationId: operation.operationId,
|
97
|
+
summary: operation.summary,
|
98
|
+
description: operation.description,
|
99
|
+
parameters: operation.parameters,
|
100
|
+
requestBody: operation.requestBody,
|
101
|
+
responses: operation.responses,
|
102
|
+
});
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
return operations;
|
107
|
+
}
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Generate a valid JavaScript identifier from a string
|
111
|
+
*/
|
112
|
+
private toValidIdentifier(str: string): string {
|
113
|
+
return str
|
114
|
+
.replace(/[^a-zA-Z0-9_$]/g, '_')
|
115
|
+
.replace(/^[0-9]/, '_$&')
|
116
|
+
.replace(/_+/g, '_')
|
117
|
+
.replace(/^_|_$/g, '');
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Generate method name from operation info
|
122
|
+
*/
|
123
|
+
private generateMethodName(operation: OperationInfo): string {
|
124
|
+
if (operation.operationId) {
|
125
|
+
// Clean up HubSpot-style operationIds like "get-/crm/v3/objects/companies_getPage"
|
126
|
+
let cleaned = operation.operationId;
|
127
|
+
|
128
|
+
// Extract the last part after underscore if it exists
|
129
|
+
const parts = cleaned.split('_');
|
130
|
+
if (parts.length > 1) {
|
131
|
+
const lastPart = parts[parts.length - 1];
|
132
|
+
// If the last part looks like a method name (camelCase), use it
|
133
|
+
if (lastPart && /^[a-z][a-zA-Z0-9]*$/.test(lastPart)) {
|
134
|
+
cleaned = lastPart;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
// Remove any remaining special characters and clean up
|
139
|
+
cleaned = cleaned
|
140
|
+
.replace(/^(get|post|put|patch|delete|head|options)-/i, '') // Remove HTTP method prefix
|
141
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
142
|
+
.replace(/_+/g, '_')
|
143
|
+
.replace(/^_|_$/g, '');
|
144
|
+
|
145
|
+
// If we still have a valid identifier, use it
|
146
|
+
if (cleaned && /^[a-zA-Z]/.test(cleaned)) {
|
147
|
+
return cleaned;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
// Generate from method + path
|
152
|
+
const pathParts = operation.path
|
153
|
+
.replace(/[{}]/g, '') // Remove path parameters
|
154
|
+
.split('/')
|
155
|
+
.filter((part) => part.length > 0)
|
156
|
+
.map((part) => this.toValidIdentifier(part));
|
157
|
+
|
158
|
+
const methodPrefix = operation.method.toLowerCase();
|
159
|
+
const pathSuffix = pathParts.join('_') || 'root';
|
160
|
+
|
161
|
+
return `${methodPrefix}_${pathSuffix}`;
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Generate JSDoc comment for an operation
|
166
|
+
*/
|
167
|
+
private generateJSDoc(operation: OperationInfo): string {
|
168
|
+
const lines: string[] = [];
|
169
|
+
|
170
|
+
if (operation.summary) {
|
171
|
+
lines.push(` * ${operation.summary}`);
|
172
|
+
}
|
173
|
+
|
174
|
+
if (operation.description) {
|
175
|
+
lines.push(` *`);
|
176
|
+
// Split long descriptions into multiple lines
|
177
|
+
const descLines = operation.description.split('\n');
|
178
|
+
descLines.forEach(line => {
|
179
|
+
if (line.trim()) {
|
180
|
+
lines.push(` * ${line.trim()}`);
|
181
|
+
}
|
182
|
+
});
|
183
|
+
}
|
184
|
+
|
185
|
+
// Document parameters with full details
|
186
|
+
if (operation.parameters && operation.parameters.length > 0) {
|
187
|
+
lines.push(' *');
|
188
|
+
lines.push(' * @param {Object} args - Request arguments');
|
189
|
+
|
190
|
+
for (const param of operation.parameters) {
|
191
|
+
if (typeof param === 'object' && 'name' in param) {
|
192
|
+
const paramName = param.name;
|
193
|
+
const paramDesc = param.description || '';
|
194
|
+
const paramRequired = param.required ? '(required)' : '(optional)';
|
195
|
+
const paramType = param.schema?.type || 'any';
|
196
|
+
const paramIn = param.in || '';
|
197
|
+
|
198
|
+
let paramDoc = ` * @param {${paramType}} args.${paramName} ${paramRequired}`;
|
199
|
+
if (paramDesc) {
|
200
|
+
paramDoc += ` - ${paramDesc}`;
|
201
|
+
}
|
202
|
+
if (paramIn) {
|
203
|
+
paramDoc += ` [${paramIn}]`;
|
204
|
+
}
|
205
|
+
lines.push(paramDoc);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
// Document request body
|
211
|
+
if (operation.requestBody) {
|
212
|
+
lines.push(' *');
|
213
|
+
const bodyDesc = operation.requestBody.description || 'Request body';
|
214
|
+
const required = operation.requestBody.required ? '(required)' : '(optional)';
|
215
|
+
lines.push(` * @param {Object} args.body ${required} - ${bodyDesc}`);
|
216
|
+
}
|
217
|
+
|
218
|
+
// Document response
|
219
|
+
lines.push(' *');
|
220
|
+
lines.push(` * @returns {Promise<Object>} ${operation.method} ${operation.path} response`);
|
221
|
+
|
222
|
+
return lines.join('\n');
|
223
|
+
}
|
224
|
+
|
225
|
+
/**
|
226
|
+
* Get the number of operations in the OpenAPI spec
|
227
|
+
*/
|
228
|
+
getOperationsCount(): number {
|
229
|
+
return this.extractOperations().length;
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Generate the connector controller code
|
234
|
+
*/
|
235
|
+
generateController(): string {
|
236
|
+
const operations = this.extractOperations();
|
237
|
+
|
238
|
+
if (operations.length === 0) {
|
239
|
+
throw new Error('No operations found in OpenAPI specification');
|
240
|
+
}
|
241
|
+
|
242
|
+
const methods = operations
|
243
|
+
.map((operation) => {
|
244
|
+
const methodName = this.generateMethodName(operation);
|
245
|
+
const jsdoc = this.generateJSDoc(operation);
|
246
|
+
|
247
|
+
return ` /**\n${jsdoc}\n */\n async ${methodName}(args: any) {\n // TODO: Implement ${operation.method} ${operation.path}\n throw new Error('Method not implemented');\n }`;
|
248
|
+
})
|
249
|
+
.join('\n\n');
|
250
|
+
|
251
|
+
return `import {AbstractController} from '@aloma.io/integration-sdk';
|
252
|
+
|
253
|
+
export default class Controller extends AbstractController {
|
254
|
+
|
255
|
+
${methods}
|
256
|
+
}`;
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
// CLI setup
|
261
|
+
const program = new Command();
|
262
|
+
|
263
|
+
program
|
264
|
+
.name('openapi-to-connector')
|
265
|
+
.description('Generate a connector controller from an OpenAPI specification')
|
266
|
+
.version('1.0.0')
|
267
|
+
.showHelpAfterError();
|
268
|
+
|
269
|
+
program
|
270
|
+
.command('generate')
|
271
|
+
.description('Generate connector controller from OpenAPI spec')
|
272
|
+
.requiredOption('--name <name>', 'Human-readable connector name')
|
273
|
+
.requiredOption('--spec <file>', 'OpenAPI specification file (JSON or YAML)')
|
274
|
+
.option('--out <file>', 'Output file path', 'index.mts')
|
275
|
+
.action(async (options) => {
|
276
|
+
try {
|
277
|
+
// Read and parse the OpenAPI spec
|
278
|
+
const specContent = fs.readFileSync(options.spec, 'utf-8');
|
279
|
+
const spec = OpenAPIToConnector.parseSpec(specContent);
|
280
|
+
|
281
|
+
// Generate the connector controller
|
282
|
+
const generator = new OpenAPIToConnector(spec, options.name);
|
283
|
+
const controllerCode = generator.generateController();
|
284
|
+
|
285
|
+
// Write the output file
|
286
|
+
fs.writeFileSync(options.out, controllerCode);
|
287
|
+
|
288
|
+
console.log(`✅ Successfully generated connector controller: ${options.out}`);
|
289
|
+
console.log(`📝 Connector name: ${options.name}`);
|
290
|
+
console.log(`📊 Found ${generator['extractOperations']().length} operations`);
|
291
|
+
} catch (error) {
|
292
|
+
console.error('❌ Error:', error instanceof Error ? error.message : 'Unknown error');
|
293
|
+
process.exit(1);
|
294
|
+
}
|
295
|
+
});
|
296
|
+
|
297
|
+
// Only run CLI if this file is executed directly
|
298
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
299
|
+
// If no command is provided, show help
|
300
|
+
if (process.argv.length <= 2) {
|
301
|
+
program.help();
|
302
|
+
} else {
|
303
|
+
program.parse();
|
304
|
+
}
|
305
|
+
}
|