@keragon/connector-cli 0.0.1
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/assets/index.ts +3 -0
- package/assets/templates/add/action/src/components/<%= componentName %>/<%= componentName %>.definition.json.ejs +113 -0
- package/assets/templates/add/action/src/components/<%= componentName %>/index.ts.ejs +5 -0
- package/assets/templates/add/implementation/connector/__tests__/<%= name %>.test.ts.ejs +28 -0
- package/assets/templates/add/implementation/connector/src/<%= name %>.ts.ejs +16 -0
- package/assets/templates/add/pollingtrigger/__tests__/<%= componentName %>.test.ts.ejs +73 -0
- package/assets/templates/add/pollingtrigger/src/components/<%= componentName %>/<%= componentName %>.definition.json.ejs +48 -0
- package/assets/templates/add/pollingtrigger/src/components/<%= componentName %>/<%= componentName %>.ts.ejs +32 -0
- package/assets/templates/add/pollingtrigger/src/components/<%= componentName %>/index.ts.ejs +7 -0
- package/assets/templates/add/trigger/__tests__/<%= componentName %>.test.ts.ejs +154 -0
- package/assets/templates/add/trigger/src/components/<%= componentName %>/<%= componentName %>.definition.json.ejs +42 -0
- package/assets/templates/add/trigger/src/components/<%= componentName %>/<%= componentName %>.ts.ejs +76 -0
- package/assets/templates/add/trigger/src/components/<%= componentName %>/index.ts.ejs +7 -0
- package/assets/templates/create/connector/README.md.ejs +1 -0
- package/assets/templates/create/connector/keragon.json.ejs +3 -0
- package/assets/templates/create/connector/package.json.ejs +18 -0
- package/assets/templates/create/connector/src/<%= name %>.definition.json.ejs +10 -0
- package/assets/templates/create/connector/src/components/index.ts.ejs +2 -0
- package/assets/templates/create/connector/src/index.ts.ejs +9 -0
- package/assets/templates/create/connector/tsconfig.json.ejs +22 -0
- package/assets/templates/create/connector/tsconfig.lib.json.ejs +18 -0
- package/assets/templates/create/connector/tsconfig.spec.json.ejs +17 -0
- package/main.js +2 -0
- package/package.json +62 -0
package/assets/index.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<%
|
|
2
|
+
function formatComponentTitle(str) {
|
|
3
|
+
const spaced = str?.replace(/([A-Z])/g, ' $1')?.toLowerCase();
|
|
4
|
+
return spaced?.charAt(0).toUpperCase() + spaced?.slice(1);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const obj = {
|
|
8
|
+
id: componentId,
|
|
9
|
+
title: formatComponentTitle(componentName),
|
|
10
|
+
description: desc,
|
|
11
|
+
type: "action",
|
|
12
|
+
sdkVersion: "0.0.1"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
if (typeof alert !== 'undefined') {
|
|
16
|
+
obj.alerts = [{
|
|
17
|
+
type: "info",
|
|
18
|
+
content: `More info in [article](https://go.keragon.com/${componentName})`
|
|
19
|
+
}];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof http !== 'undefined') {
|
|
23
|
+
obj.request = {
|
|
24
|
+
config: {
|
|
25
|
+
method: "get",
|
|
26
|
+
url: "/"
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof pagination !== 'undefined') {
|
|
32
|
+
obj.pagination = {
|
|
33
|
+
strategy: pagination,
|
|
34
|
+
maxItems: "${ .inputs.maxItems // 250 }"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (pagination === 'offset') {
|
|
38
|
+
Object.assign(obj.pagination, {
|
|
39
|
+
pageSize: 100,
|
|
40
|
+
setPageSize: "${ .request.params.limit = .pagination.pageSize }",
|
|
41
|
+
setPageToken: "${ .request.params.offset = .pagination.nextPageToken }"
|
|
42
|
+
});
|
|
43
|
+
} else if (pagination === 'page') {
|
|
44
|
+
Object.assign(obj.pagination, {
|
|
45
|
+
pageSize: 100,
|
|
46
|
+
setPageSize: "${ .request.params.per_page = .pagination.pageSize }",
|
|
47
|
+
setPageToken: "${ .request.params.page = .pagination.nextPageToken + 1 }"
|
|
48
|
+
});
|
|
49
|
+
} else if (pagination === 'cursor') {
|
|
50
|
+
Object.assign(obj.pagination, {
|
|
51
|
+
setPageToken: "${ .request.url = .pagination.nextPageToken }",
|
|
52
|
+
getNextPageToken: "${ .response.data.next }"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof errorHandler !== 'undefined') {
|
|
58
|
+
obj.response = {
|
|
59
|
+
errorHandlers: [{
|
|
60
|
+
condition: "${ .response.status != 200 }",
|
|
61
|
+
action: "throwError",
|
|
62
|
+
message: "${ .response.data | tostring }"
|
|
63
|
+
}]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof inputs !== 'undefined') {
|
|
68
|
+
obj.inputs = {
|
|
69
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
70
|
+
type: "object",
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
required: ["id"],
|
|
73
|
+
properties: {
|
|
74
|
+
id: {
|
|
75
|
+
type: "string",
|
|
76
|
+
title: "ID",
|
|
77
|
+
dynamicEnum: {
|
|
78
|
+
componentId: `${connectorId}.get`,
|
|
79
|
+
processInputs: "${ }",
|
|
80
|
+
processOutputs: "${ [.[] | {label: .name, value: .id}] }"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (typeof pagination !== 'undefined') {
|
|
87
|
+
obj.inputs.properties.maxItems = {
|
|
88
|
+
type: "number",
|
|
89
|
+
title: "Max items",
|
|
90
|
+
default: 250
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof outputs !== 'undefined') {
|
|
96
|
+
obj.outputs = {
|
|
97
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
98
|
+
type: "array",
|
|
99
|
+
title: "Items",
|
|
100
|
+
properties: {
|
|
101
|
+
id: {
|
|
102
|
+
type: "string",
|
|
103
|
+
title: "ID"
|
|
104
|
+
},
|
|
105
|
+
name: {
|
|
106
|
+
type: "string",
|
|
107
|
+
title: "Name"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
%>
|
|
113
|
+
<%- JSON.stringify(obj, null, 2) %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Credentials
|
|
3
|
+
} from '@keragon/connector-sdk';
|
|
4
|
+
import app from '../src';
|
|
5
|
+
const { definition, ctor: <%= firstLetterUpperCase(name) %> } = app;
|
|
6
|
+
|
|
7
|
+
const setupTest = ({
|
|
8
|
+
credentials = {
|
|
9
|
+
authScheme: {
|
|
10
|
+
id: 'com.keragon.<%= name %>.environments.prod.authSchemes.custom',
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
}: {
|
|
14
|
+
credentials?: Credentials
|
|
15
|
+
}) => {
|
|
16
|
+
const <%= name %> = new <%= firstLetterUpperCase(name) %>({
|
|
17
|
+
definition, credentials,
|
|
18
|
+
});
|
|
19
|
+
return { <%= name %>, credentials }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('Connector: <%= firstLetterUpperCase(name) %> should', () => {
|
|
23
|
+
it('construct connector', async () => {
|
|
24
|
+
const { <%= name %> } = setupTest({});
|
|
25
|
+
|
|
26
|
+
expect(<%= name %>).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigureCredentialsArgs,
|
|
3
|
+
<%= isHttp ? 'HttpApp' : 'App' %>,
|
|
4
|
+
} from '@keragon/connector-sdk';
|
|
5
|
+
|
|
6
|
+
export default class <%= firstLetterUpperCase(name) %> extends <%= isHttp ? 'HttpApp' : 'App' %> {
|
|
7
|
+
// TODO: default implementation
|
|
8
|
+
override async configureCredentials(args: ConfigureCredentialsArgs) {
|
|
9
|
+
const configuredCredentials = await super.configureCredentials(args);
|
|
10
|
+
const { authScheme } = configuredCredentials;
|
|
11
|
+
return {
|
|
12
|
+
...configuredCredentials,
|
|
13
|
+
authScheme,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CloudEventData } from '@keragon/types';
|
|
2
|
+
import { CloudEvent } from 'cloudevents';
|
|
3
|
+
import app from '../src';
|
|
4
|
+
const { definition, ctor: <%= firstLetterUpperCase(connectorId) %> } = app;
|
|
5
|
+
|
|
6
|
+
const mockAxiosGet = jest.fn();
|
|
7
|
+
const mockAxiosPost = jest.fn();
|
|
8
|
+
const mockAxiosPut = jest.fn();
|
|
9
|
+
const mockAxiosDelete = jest.fn();
|
|
10
|
+
const mockIsAxiosError = jest.fn();
|
|
11
|
+
jest.mock('axios', () => ({
|
|
12
|
+
create: jest.fn((defaults) => ({
|
|
13
|
+
get: mockAxiosGet,
|
|
14
|
+
post: mockAxiosPost,
|
|
15
|
+
put: mockAxiosPut,
|
|
16
|
+
delete: mockAxiosDelete,
|
|
17
|
+
defaults,
|
|
18
|
+
})),
|
|
19
|
+
isAxiosError: jest.fn().mockImplementation(() => (mockIsAxiosError())),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
//Mock Date.now
|
|
23
|
+
const now = 1738876122999;
|
|
24
|
+
jest.useFakeTimers({ now });
|
|
25
|
+
|
|
26
|
+
const setUpTest = ({
|
|
27
|
+
store,
|
|
28
|
+
}: {
|
|
29
|
+
store?: Record<string, unknown>;
|
|
30
|
+
}) => {
|
|
31
|
+
const <%= connectorId %>: InstanceType<typeof <%= firstLetterUpperCase(connectorId) %>> = new <%= firstLetterUpperCase(connectorId) %>({
|
|
32
|
+
definition,
|
|
33
|
+
credentials: {
|
|
34
|
+
key: 'mockKey',
|
|
35
|
+
authScheme: {
|
|
36
|
+
// TODO authScheme
|
|
37
|
+
id: 'com.keragon.<%= connectorId %>.environments.prod.authSchemes.apiKey',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const trigger = <%= connectorId %>.constructComponent({ componentId: 'com.keragon.<%= connectorId %>.<%= componentId %>', store });
|
|
43
|
+
return { <%= connectorId %>, trigger, webhookUrl: 'https://test.com/v1/' };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe('Component: Trigger com.keragon.<%= connectorId %>.<%= componentId %> should', () => {
|
|
47
|
+
|
|
48
|
+
it('.generateSampleEvents returns sample', async () => {
|
|
49
|
+
// Arrange
|
|
50
|
+
const { trigger } = setupTest();
|
|
51
|
+
const mockGetDynamicValue = jest.fn();
|
|
52
|
+
mockGetDynamicValue
|
|
53
|
+
.mockResolvedValue([
|
|
54
|
+
// TODO
|
|
55
|
+
]);
|
|
56
|
+
trigger.getDynamicValue = mockGetDynamicValue;
|
|
57
|
+
jest.useFakeTimers({ now: 1730123456789 });
|
|
58
|
+
|
|
59
|
+
// Act
|
|
60
|
+
const results = await trigger.generateSampleEvents({});
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(mockGetDynamicValue).toHaveBeenCalledWith({
|
|
64
|
+
componentId: 'com.keragon.<%= connectorId %>.getItems',
|
|
65
|
+
processInputs: '${ {} }',
|
|
66
|
+
processOutputs: '${ }',
|
|
67
|
+
});
|
|
68
|
+
expect(results).toEqual([
|
|
69
|
+
// TODO
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "<%= componentId %>",
|
|
3
|
+
"title": "<%= title %>",
|
|
4
|
+
"description": "<%= desc %>",
|
|
5
|
+
"type": "trigger",
|
|
6
|
+
"sdkVersion": "0.0.1"
|
|
7
|
+
<% if (typeof alert !== 'undefined') { %>
|
|
8
|
+
,"alerts": [{
|
|
9
|
+
"type": "info",
|
|
10
|
+
"content": "Due to vendor limitations you can have only one polling trigger"
|
|
11
|
+
}]
|
|
12
|
+
<% } %>,
|
|
13
|
+
"poll": {
|
|
14
|
+
"schedule": {
|
|
15
|
+
"cron": "*/5 * * * *"
|
|
16
|
+
},
|
|
17
|
+
"dedup": {
|
|
18
|
+
"strategy": "lastTimestamp",
|
|
19
|
+
"getDedupValue": "${ .item.created_time }"
|
|
20
|
+
},
|
|
21
|
+
"poller": {
|
|
22
|
+
"componentId": "<%= connectorId %>.getItems<TODO set correct component>",
|
|
23
|
+
"processInputs": "${ { event: .event } }",
|
|
24
|
+
"processOutputs": "${ . | sort_by(.created_time) | reverse }"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
<% if (typeof inputs !== 'undefined') { %>
|
|
28
|
+
,"inputs": {
|
|
29
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"required": [
|
|
33
|
+
"event"
|
|
34
|
+
],
|
|
35
|
+
"properties": {
|
|
36
|
+
"event": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"title": "Event",
|
|
39
|
+
"dynamicEnum": {
|
|
40
|
+
"componentId": "<%= connectorId %>.get",
|
|
41
|
+
"processInputs": "${ }",
|
|
42
|
+
"processOutputs": "${ [.[] | {label: .name, value: .type}] }"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
<% } %>
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HasSampleEvents,
|
|
3
|
+
SampleEvent,
|
|
4
|
+
PollingTrigger,
|
|
5
|
+
App,
|
|
6
|
+
} from '@keragon/connector-sdk';
|
|
7
|
+
|
|
8
|
+
export default class <%= firstLetterUpperCase(componentName) %> extends PollingTrigger<App> implements HasSampleEvents {
|
|
9
|
+
|
|
10
|
+
async generateSampleEvents({ store: _, ...inputs }: Record<string, unknown>): Promise<SampleEvent[]> {
|
|
11
|
+
const { a, b, c } = this.validateInputs(inputs);
|
|
12
|
+
const items = await this.getDynamicValue({
|
|
13
|
+
componentId: '<%= connectorId %>.getItems<TODO set correct component>',
|
|
14
|
+
processInputs: `\${ ${JSON.stringify({ a, b, c })} }`,
|
|
15
|
+
processOutputs: '${ sort_by(.date) | reverse }'
|
|
16
|
+
});
|
|
17
|
+
if (!Array.isArray(items)) {
|
|
18
|
+
throw new Error('items must be an array');
|
|
19
|
+
}
|
|
20
|
+
return items.slice(0, 5)
|
|
21
|
+
.map((i) => ({
|
|
22
|
+
label: `Sample item ${i}`,
|
|
23
|
+
value: {
|
|
24
|
+
data: {
|
|
25
|
+
body: i,
|
|
26
|
+
headers: {},
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { CloudEventData } from '@keragon/types';
|
|
2
|
+
import { CloudEvent } from 'cloudevents';
|
|
3
|
+
import app from '../src';
|
|
4
|
+
const { definition, ctor: <%= firstLetterUpperCase(connectorId) %> } = app;
|
|
5
|
+
|
|
6
|
+
const mockAxiosGet = jest.fn();
|
|
7
|
+
const mockAxiosPost = jest.fn();
|
|
8
|
+
const mockAxiosPut = jest.fn();
|
|
9
|
+
const mockAxiosDelete = jest.fn();
|
|
10
|
+
const mockIsAxiosError = jest.fn();
|
|
11
|
+
jest.mock('axios', () => ({
|
|
12
|
+
create: jest.fn((defaults) => ({
|
|
13
|
+
get: mockAxiosGet,
|
|
14
|
+
post: mockAxiosPost,
|
|
15
|
+
put: mockAxiosPut,
|
|
16
|
+
delete: mockAxiosDelete,
|
|
17
|
+
defaults,
|
|
18
|
+
})),
|
|
19
|
+
isAxiosError: jest.fn().mockImplementation(() => (mockIsAxiosError())),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
//Mock Date.now
|
|
23
|
+
const now = 1738876122999;
|
|
24
|
+
jest.useFakeTimers({ now });
|
|
25
|
+
|
|
26
|
+
const setUpTest = ({
|
|
27
|
+
store,
|
|
28
|
+
}: {
|
|
29
|
+
store?: Record<string, unknown>;
|
|
30
|
+
}) => {
|
|
31
|
+
const <%= connectorId %>: InstanceType<typeof <%= firstLetterUpperCase(connectorId) %>> = new <%= firstLetterUpperCase(connectorId) %>({
|
|
32
|
+
definition,
|
|
33
|
+
credentials: {
|
|
34
|
+
key: 'mockKey',
|
|
35
|
+
authScheme: {
|
|
36
|
+
// TODO authScheme
|
|
37
|
+
id: 'com.keragon.<%= connectorId %>.environments.prod.authSchemes.apiKey',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const trigger = <%= connectorId %>.constructComponent({ componentId: 'com.keragon.<%= connectorId %>.<%= componentId %>', store });
|
|
43
|
+
return { <%= connectorId %>, trigger, webhookUrl: 'https://test.com/v1/' };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe('Component: Trigger com.keragon.<%= connectorId %>.<%= componentId %> should', () => {
|
|
47
|
+
|
|
48
|
+
describe('implement configure() and', () => {
|
|
49
|
+
|
|
50
|
+
it('register the webhook', async () => {
|
|
51
|
+
const { trigger, webhookUrl } = setUpTest({});
|
|
52
|
+
|
|
53
|
+
// Arrange: Mock request to register the webhook
|
|
54
|
+
mockAxiosPost.mockResolvedValue({ data: { data: { id: 'mockWebhookId' } } });
|
|
55
|
+
const inputs = {
|
|
56
|
+
// TODO
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Act
|
|
60
|
+
const configuredInputs = await trigger.configure(inputs, webhookUrl);
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(mockAxiosPost).toHaveBeenCalledWith('/', {
|
|
64
|
+
data: {
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Assert that the webhook id, url and secret were stored
|
|
69
|
+
expect(trigger.store.get('webhookURL')).toEqual('https://test.com/v2/?triggerId=com.keragon.<%= connectorId %>.<%= componentId %>');
|
|
70
|
+
expect(trigger.store.get('webhookID')).toEqual('mockWebhookId');
|
|
71
|
+
|
|
72
|
+
// Assert that the configured inputs are returned
|
|
73
|
+
expect(configuredInputs).toEqual(inputs);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('deactivate an existing webhook before create a new one', async () => {
|
|
77
|
+
// Arrange : setup
|
|
78
|
+
const { trigger, webhookUrl } = setUpTest({
|
|
79
|
+
store: {
|
|
80
|
+
webhookURL: 'webhookURL',
|
|
81
|
+
webhookID: 'mockWebhookId1',
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const inputs = {
|
|
85
|
+
}
|
|
86
|
+
// Arrange: Mock request to delete the old webhook
|
|
87
|
+
mockAxiosDelete.mockResolvedValue({ data: {} });
|
|
88
|
+
// Arrange: Mock request to create the webhook
|
|
89
|
+
mockAxiosPost.mockResolvedValue({ data: { data: { id: 'mockWebhookId2' } } });
|
|
90
|
+
|
|
91
|
+
// Act
|
|
92
|
+
await trigger.configure(inputs, webhookUrl);
|
|
93
|
+
|
|
94
|
+
// Assert
|
|
95
|
+
expect(mockAxiosDelete).toHaveBeenCalledWith('/webhooks/mockWebhookId1');
|
|
96
|
+
expect(mockAxiosPost).toHaveBeenCalledWith('/webhooks', expect.any(Object));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('implement run() and', () => {
|
|
102
|
+
|
|
103
|
+
it('fail if webhook request could not have data', async () => {
|
|
104
|
+
const { trigger } = setUpTest({
|
|
105
|
+
store: {}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const event = {} as CloudEvent<CloudEventData>;
|
|
109
|
+
|
|
110
|
+
await expect(trigger.run({ event })).rejects.toThrow('Component com.keragon.<%= connectorId %>.<%= componentId %> run() received no event payload');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('return passed events', async () => {
|
|
114
|
+
const { trigger } = setUpTest({
|
|
115
|
+
store: {}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const mockEvent = {
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const event = { data: { body: { events: [mockEvent] } } } as unknown as CloudEvent<CloudEventData>;
|
|
122
|
+
|
|
123
|
+
const output = await trigger.run({ event });
|
|
124
|
+
|
|
125
|
+
expect(output).toEqual({ result: [mockEvent] });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('implement deactivate() and', () => {
|
|
130
|
+
|
|
131
|
+
it('deletes the webhook', async () => {
|
|
132
|
+
const { trigger } = setUpTest({
|
|
133
|
+
store: {
|
|
134
|
+
webhookID: 'mockWebhookId',
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Arrange: Mock request to unregister the webhook
|
|
139
|
+
mockAxiosDelete.mockResolvedValue({});
|
|
140
|
+
|
|
141
|
+
// Act
|
|
142
|
+
await trigger.deactivate?.();
|
|
143
|
+
|
|
144
|
+
// Assert that the webhook was registered with the correct args
|
|
145
|
+
expect(mockAxiosDelete).toHaveBeenCalledWith('/webhooks/mockWebhookId');
|
|
146
|
+
|
|
147
|
+
// Assert that the store is cleared
|
|
148
|
+
expect(trigger.store.get('webhookURL')).toBeUndefined()
|
|
149
|
+
expect(trigger.store.get('webhookID')).toBeUndefined()
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "<%= componentId %>",
|
|
3
|
+
"title": "<%= title %>",
|
|
4
|
+
"description": "<%= desc %>",
|
|
5
|
+
"type": "trigger",
|
|
6
|
+
"sdkVersion": "0.0.1"
|
|
7
|
+
<% if (typeof alert !== 'undefined') { %>
|
|
8
|
+
,"alerts": [{
|
|
9
|
+
"type": "info",
|
|
10
|
+
"content": "Please follow the [article](https://go.keragon.com/<%= componentName %>) to set up the trigger"
|
|
11
|
+
}]
|
|
12
|
+
<% } %>
|
|
13
|
+
<% if (typeof http !== 'undefined') { %>
|
|
14
|
+
,"request": {
|
|
15
|
+
"config": {
|
|
16
|
+
"method": "get",
|
|
17
|
+
"url": "/"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
<% } %>
|
|
21
|
+
<% if (typeof inputs !== 'undefined') { %>
|
|
22
|
+
,"inputs": {
|
|
23
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": [
|
|
27
|
+
"event"
|
|
28
|
+
],
|
|
29
|
+
"properties": {
|
|
30
|
+
"event": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"title": "Event",
|
|
33
|
+
"dynamicEnum": {
|
|
34
|
+
"componentId": "<%= connectorId %>.get",
|
|
35
|
+
"processInputs": "${ }",
|
|
36
|
+
"processOutputs": "${ [.[] | {label: .name, value: .type}] }"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
<% } %>
|
|
42
|
+
}
|
package/assets/templates/add/trigger/src/components/<%= componentName %>/<%= componentName %>.ts.ejs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HasSampleEvents,
|
|
3
|
+
SampleEvent,
|
|
4
|
+
extractErrorMessage,
|
|
5
|
+
RunFunctionReturnType,
|
|
6
|
+
RunFunctionArgs,
|
|
7
|
+
<%= http ? 'HttpApp' : 'App' %>,
|
|
8
|
+
<%= http ? 'HttpComponent' : 'Component' %>,
|
|
9
|
+
} from '@keragon/connector-sdk';
|
|
10
|
+
|
|
11
|
+
const WEBHOOK_ID = 'webhookID';
|
|
12
|
+
const WEBHOOK_URL = 'webhookURL';
|
|
13
|
+
|
|
14
|
+
export default class <%= firstLetterUpperCase(componentName) %> extends <%- http ? 'HttpComponent<HttpApp>' : 'Component<App>' %> implements HasSampleEvents {
|
|
15
|
+
|
|
16
|
+
override async configure(inputs: Record<string, unknown> = {}, webhookURL: string, ..._rest: string[]) {
|
|
17
|
+
if (this.store.get(WEBHOOK_ID)) {
|
|
18
|
+
await this.deactivate();
|
|
19
|
+
}
|
|
20
|
+
const configuredInputs = await super.configure(inputs);
|
|
21
|
+
const url = new URL(webhookURL);
|
|
22
|
+
url.pathname = url.pathname.replace('/v1/', '/v2/');
|
|
23
|
+
url.searchParams.set('triggerId', this.definition.id.toLowerCase());
|
|
24
|
+
this.store.set(WEBHOOK_URL, url.toString());
|
|
25
|
+
this.store.set(WEBHOOK_URL, url.toString());
|
|
26
|
+
|
|
27
|
+
const client = await this.app.getClient();
|
|
28
|
+
try {
|
|
29
|
+
const { data: { id } } = await client.post('/', {});
|
|
30
|
+
this.store.set(WEBHOOK_URL, webhookURL);
|
|
31
|
+
this.store.set(WEBHOOK_ID, id);
|
|
32
|
+
return configuredInputs;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Failed to configure ${this.definition.id} because ${extractErrorMessage(error)}.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override async run({ event }: RunFunctionArgs): Promise<RunFunctionReturnType> {
|
|
39
|
+
const componentId = this.definition.id;
|
|
40
|
+
if (!event?.data)
|
|
41
|
+
throw new Error(`Component ${componentId} run() received no event payload`);
|
|
42
|
+
const data = event.data.body;
|
|
43
|
+
return { result: data };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override async deactivate() {
|
|
47
|
+
try {
|
|
48
|
+
this.store.delete(WEBHOOK_URL);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`Failed to deactivate "${this.definition.id}" because ${extractErrorMessage(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async generateSampleEvents({ store: _, ...inputs }: Record<string, unknown>): Promise<SampleEvent[]> {
|
|
55
|
+
const { a, b, c } = this.validateInputs(inputs);
|
|
56
|
+
const items = await this.getDynamicValue({
|
|
57
|
+
componentId: '<%= connectorId %>.getItems<TODO set correct component>',
|
|
58
|
+
processInputs: `\${ ${JSON.stringify({ a, b, c })} }`,
|
|
59
|
+
processOutputs: '${ sort_by(.date) | reverse }'
|
|
60
|
+
});
|
|
61
|
+
if (!Array.isArray(items)) {
|
|
62
|
+
throw new Error('items must be an array');
|
|
63
|
+
}
|
|
64
|
+
return items.slice(0, 5)
|
|
65
|
+
.map((i) => ({
|
|
66
|
+
label: `Sample item ${i}`,
|
|
67
|
+
value: {
|
|
68
|
+
data: {
|
|
69
|
+
body: i,
|
|
70
|
+
headers: {},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# <%= name %> connector
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keragonhq/<%= name %>",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Keragon's built-in <%= firstLetterUpperCase(name) %> app",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"registry": "https://npm.pkg.github.com",
|
|
8
|
+
"access": "restricted"
|
|
9
|
+
},
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@keragon/connector-sdk": "0.0.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"jest": "^29.7.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
<%= http ? 'createHttpAppDescriptor' : 'createAppDescriptor' %>
|
|
3
|
+
} from '@keragon/connector-sdk';
|
|
4
|
+
import components from './components';
|
|
5
|
+
import definition from './<%= name %>.definition.json';
|
|
6
|
+
|
|
7
|
+
export default <%= http ? 'createHttpAppDescriptor' : 'createAppDescriptor' %> ({
|
|
8
|
+
definition: { ...definition, components },
|
|
9
|
+
});
|