Skip to content

Commit 6e06e2e

Browse files
authored
feat: add workflow to keep integrations up-to-date (#232)
* feat(integrations): add workflow for automatic updates * update to use app
1 parent 6ce30ae commit 6e06e2e

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
const fs = require('fs');
2+
const fsPromises = require('fs').promises;
3+
const path = require('path');
4+
5+
async function getIntegrations() {
6+
const bearerToken = process.env.BOTPRESS_TOKEN;
7+
const workspaceId = process.env.BOTPRESS_WORKSPACE_ID;
8+
9+
if (!bearerToken) {
10+
throw new Error('BOTPRESS_TOKEN environment variable is required');
11+
}
12+
13+
if (!workspaceId) {
14+
throw new Error('BOTPRESS_WORKSPACE_ID environment variable is required');
15+
}
16+
17+
const options = {
18+
method: 'GET',
19+
headers: {
20+
Authorization: `Bearer ${bearerToken}`,
21+
'x-workspace-id': workspaceId
22+
},
23+
body: undefined
24+
};
25+
26+
let allIntegrations = [];
27+
let nextToken = null;
28+
let pageCount = 0;
29+
30+
try {
31+
do {
32+
pageCount++;
33+
console.log(`Fetching integrations page ${pageCount}${nextToken ? ` (token: ${nextToken.substring(0, 20)}...)` : ''}`);
34+
35+
let url = 'https://api.botpress.cloud/v1/admin/hub/integrations?sortBy=name&limit=1000&version=latest';
36+
if (nextToken) {
37+
url += `&nextToken=${encodeURIComponent(nextToken)}`;
38+
}
39+
40+
const response = await fetch(url, options);
41+
if (!response.ok) {
42+
throw new Error(`HTTP error! status: ${response.status}`);
43+
}
44+
45+
const data = await response.json();
46+
47+
if (data.integrations && Array.isArray(data.integrations)) {
48+
allIntegrations = allIntegrations.concat(data.integrations);
49+
console.log(`Retrieved ${data.integrations.length} integrations from page ${pageCount} (total: ${allIntegrations.length})`);
50+
}
51+
52+
nextToken = data.meta?.nextToken || null;
53+
54+
} while (nextToken);
55+
56+
console.log(`Completed pagination. Total integrations retrieved: ${allIntegrations.length}`);
57+
58+
return {
59+
integrations: allIntegrations
60+
};
61+
} catch (error) {
62+
console.error('Error fetching integrations:', error);
63+
throw error;
64+
}
65+
}
66+
67+
function filterIntegrations(data) {
68+
if (!data || !data.integrations) {
69+
return {};
70+
}
71+
72+
const filtered = data.integrations
73+
.filter(integration => integration.ownerWorkspace?.handle === 'botpress')
74+
.reduce((acc, integration) => {
75+
acc[integration.name] = {
76+
'version': integration.version,
77+
'id': integration.id
78+
}
79+
return acc;
80+
}, {});
81+
82+
return filtered;
83+
}
84+
85+
async function getCurrentVersions() {
86+
try {
87+
const filePath = './snippets/integrations/versions.mdx';
88+
const content = await fsPromises.readFile(filePath, 'utf8');
89+
90+
const match = content.match(/export const integrationVersions = ({[\s\S]*})/);
91+
if (!match) {
92+
throw new Error('Could not parse current versions file');
93+
}
94+
95+
return JSON.parse(match[1]);
96+
} catch (error) {
97+
console.error('Error reading current versions:', error);
98+
throw error;
99+
}
100+
}
101+
102+
function compareVersions(current, latest) {
103+
const updates = [];
104+
const newIntegrations = [];
105+
106+
for (const [name, latestInfo] of Object.entries(latest)) {
107+
if (current[name]) {
108+
const versionChanged = current[name].version !== latestInfo.version;
109+
const idChanged = current[name].id !== latestInfo.id;
110+
111+
if (versionChanged || idChanged) {
112+
updates.push({
113+
name,
114+
currentVersion: current[name].version,
115+
latestVersion: latestInfo.version,
116+
currentId: current[name].id,
117+
latestId: latestInfo.id,
118+
versionChanged,
119+
idChanged
120+
});
121+
}
122+
} else {
123+
newIntegrations.push({
124+
name,
125+
version: latestInfo.version,
126+
id: latestInfo.id
127+
});
128+
}
129+
}
130+
131+
return { updates, newIntegrations };
132+
}
133+
134+
async function updateVersionsFile(latestVersions) {
135+
const content = `export const integrationVersions = ${JSON.stringify(latestVersions, null, 2)}`;
136+
const filePath = './snippets/integrations/versions.mdx';
137+
138+
await fsPromises.writeFile(filePath, content, 'utf8');
139+
console.log(`Updated versions file: ${filePath}`);
140+
}
141+
142+
async function setGitHubOutput(name, value) {
143+
if (process.env.GITHUB_OUTPUT) {
144+
if (typeof value === 'string' && value.includes('\n')) {
145+
const delimiter = `EOF_${Math.random().toString(36).substring(7)}`;
146+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}<<${delimiter}\n${value}\n${delimiter}\n`);
147+
} else {
148+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
149+
}
150+
} else {
151+
console.log(`Output: ${name}=${value}`);
152+
}
153+
}
154+
155+
function formatChangesSummary(updates, newIntegrations) {
156+
let summary = '';
157+
158+
if (updates.length > 0) {
159+
summary += `**Integration Updates (${updates.length}):**\n`;
160+
updates.forEach(update => {
161+
const changes = [];
162+
if (update.versionChanged) {
163+
changes.push(`${update.currentVersion} -> ${update.latestVersion}`);
164+
}
165+
if (update.idChanged) {
166+
changes.push(`${update.currentId} -> ${update.latestId}`);
167+
}
168+
summary += `- ${update.name}: ${changes.join(', ')}\n`;
169+
});
170+
}
171+
172+
if (newIntegrations.length > 0) {
173+
if (summary) summary += '\n';
174+
summary += `**New Integrations (${newIntegrations.length}):**\n`;
175+
newIntegrations.forEach(integration => {
176+
summary += `- ${integration.name}: ${integration.version} (${integration.id})\n`;
177+
});
178+
}
179+
180+
return summary.trim();
181+
}
182+
183+
async function main() {
184+
try {
185+
console.log('Fetching latest integration versions from Botpress API...');
186+
const integrations = await getIntegrations();
187+
const latestVersions = filterIntegrations(integrations);
188+
189+
console.log('Reading current versions file...');
190+
const currentVersions = await getCurrentVersions();
191+
192+
console.log('Comparing versions...');
193+
const { updates, newIntegrations } = compareVersions(currentVersions, latestVersions);
194+
195+
const hasUpdates = updates.length > 0 || newIntegrations.length > 0;
196+
197+
if (hasUpdates) {
198+
console.log(`Found ${updates.length} integration updates and ${newIntegrations.length} new integrations`);
199+
200+
await updateVersionsFile(latestVersions);
201+
202+
const changesSummary = formatChangesSummary(updates, newIntegrations);
203+
const updatedIntegrationsList = [
204+
...updates.map(u => u.name),
205+
...newIntegrations.map(i => i.name)
206+
].join(', ');
207+
208+
await setGitHubOutput('has_updates', 'true');
209+
await setGitHubOutput('changes_summary', changesSummary);
210+
await setGitHubOutput('updated_integrations', updatedIntegrationsList);
211+
212+
console.log('Changes Summary:');
213+
console.log(changesSummary);
214+
} else {
215+
console.log('No integration updates found');
216+
await setGitHubOutput('has_updates', 'false');
217+
}
218+
219+
process.exit(0);
220+
} catch (error) {
221+
console.error('Error in version check:', error);
222+
await setGitHubOutput('has_updates', 'false');
223+
process.exit(1);
224+
}
225+
}
226+
227+
if (require.main === module) {
228+
main();
229+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Update Integration Versions
2+
3+
on:
4+
schedule:
5+
- cron: '0 9 * * 1'
6+
workflow_dispatch:
7+
8+
jobs:
9+
check-integration-versions:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/create-github-app-token@v2
13+
id: generate-token
14+
with:
15+
app-id: ${{ secrets.APP_ID }}
16+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
17+
18+
- name: Checkout repository
19+
uses: actions/checkout@v5
20+
with:
21+
token: ${{ steps.generate-token.outputs.token }}
22+
fetch-depth: 0
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v5
26+
with:
27+
node-version: '22'
28+
29+
- name: Install dependencies
30+
run: npm install
31+
32+
- name: Check for version updates
33+
id: check_versions
34+
env:
35+
BOTPRESS_TOKEN: ${{ secrets.BOTPRESS_TOKEN }}
36+
BOTPRESS_WORKSPACE_ID: ${{ secrets.BOTPRESS_WORKSPACE_ID }}
37+
run: |
38+
node .github/scripts/check-version-updates.js
39+
40+
- name: Create Pull Request
41+
if: steps.check_versions.outputs.has_updates == 'true'
42+
uses: peter-evans/create-pull-request@v7
43+
with:
44+
token: ${{ steps.generate-token.outputs.token }}
45+
commit-message: 'chore: update integration versions'
46+
title: 'chore: update integration versions'
47+
body: |
48+
Weekly integration version/ID update based on the latest data from the Botpress Admin API.
49+
50+
${{ steps.check_versions.outputs.changes_summary }}
51+
52+
(This PR was automatically created using a workflow)
53+
branch: update-integration-versions
54+
delete-branch: true
55+
base: main
56+
labels: |
57+
automated
58+
integrations
59+
version-update
60+
61+
- name: Output results
62+
run: |
63+
echo "Version check completed"
64+
echo "Has updates: ${{ steps.check_versions.outputs.has_updates }}"
65+
if [ "${{ steps.check_versions.outputs.has_updates }}" == "true" ]; then
66+
echo "Updated integrations: ${{ steps.check_versions.outputs.updated_integrations }}"
67+
else
68+
echo "No version updates found"
69+
fi

snippets/integrations/versions.mdx

Whitespace-only changes.

0 commit comments

Comments
 (0)