newo 3.0.0 ā 3.2.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/CHANGELOG.md +475 -347
- package/README.md +111 -0
- package/dist/api.d.ts +20 -1
- package/dist/api.js +110 -0
- package/dist/auth.js +4 -0
- package/dist/cli/commands/help.js +30 -1
- package/dist/cli/commands/list-actions.d.ts +3 -0
- package/dist/cli/commands/list-actions.js +89 -0
- package/dist/cli/commands/profile.d.ts +3 -0
- package/dist/cli/commands/profile.js +62 -0
- package/dist/cli/commands/pull-akb.d.ts +3 -0
- package/dist/cli/commands/pull-akb.js +19 -0
- package/dist/cli/commands/pull-attributes.js +7 -0
- package/dist/cli/commands/pull-integrations.d.ts +3 -0
- package/dist/cli/commands/pull-integrations.js +19 -0
- package/dist/cli/commands/push-akb.d.ts +3 -0
- package/dist/cli/commands/push-akb.js +19 -0
- package/dist/cli/commands/push-integrations.d.ts +3 -0
- package/dist/cli/commands/push-integrations.js +19 -0
- package/dist/cli/commands/sandbox.d.ts +14 -0
- package/dist/cli/commands/sandbox.js +306 -0
- package/dist/cli.js +33 -0
- package/dist/sandbox/chat.d.ts +40 -0
- package/dist/sandbox/chat.js +280 -0
- package/dist/sync/akb.d.ts +14 -0
- package/dist/sync/akb.js +175 -0
- package/dist/sync/attributes.d.ts +19 -0
- package/dist/sync/attributes.js +221 -2
- package/dist/sync/integrations.d.ts +23 -0
- package/dist/sync/integrations.js +340 -0
- package/dist/sync/projects.js +171 -1
- package/dist/sync/push.js +15 -0
- package/dist/sync/skill-files.js +1 -1
- package/dist/sync/status.js +4 -2
- package/dist/types.d.ts +209 -0
- package/package.json +14 -3
- package/src/api.ts +186 -1
- package/src/auth.ts +7 -2
- package/src/cli/commands/help.ts +30 -1
- package/src/cli/commands/list-actions.ts +112 -0
- package/src/cli/commands/profile.ts +79 -0
- package/src/cli/commands/pull-akb.ts +27 -0
- package/src/cli/commands/pull-attributes.ts +8 -0
- package/src/cli/commands/pull-integrations.ts +27 -0
- package/src/cli/commands/push-akb.ts +27 -0
- package/src/cli/commands/push-integrations.ts +27 -0
- package/src/cli/commands/sandbox.ts +365 -0
- package/src/cli.ts +41 -0
- package/src/sandbox/chat.ts +339 -0
- package/src/sync/akb.ts +205 -0
- package/src/sync/attributes.ts +269 -2
- package/src/sync/integrations.ts +403 -0
- package/src/sync/projects.ts +207 -1
- package/src/sync/push.ts +17 -0
- package/src/sync/skill-files.ts +1 -1
- package/src/sync/status.ts +4 -2
- package/src/types.ts +248 -0
package/dist/sync/attributes.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Customer attributes synchronization module
|
|
2
|
+
* Customer and project attributes synchronization module
|
|
3
3
|
*/
|
|
4
|
-
import { getCustomerAttributes } from '../api.js';
|
|
4
|
+
import { getCustomerAttributes, getProjectAttributes, listProjects, updateProjectAttribute } from '../api.js';
|
|
5
5
|
import { writeFileSafe, customerAttributesPath, customerAttributesMapPath, customerAttributesBackupPath } from '../fsutil.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs-extra';
|
|
6
8
|
import yaml from 'js-yaml';
|
|
7
9
|
/**
|
|
8
10
|
* Save customer attributes to YAML format and return content for hashing
|
|
@@ -87,4 +89,221 @@ export async function saveCustomerAttributes(client, customer, verbose = false)
|
|
|
87
89
|
throw error;
|
|
88
90
|
}
|
|
89
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Save project attributes to YAML format in project directory
|
|
94
|
+
*/
|
|
95
|
+
export async function saveProjectAttributes(client, customer, projectId, projectIdn, verbose = false) {
|
|
96
|
+
if (verbose)
|
|
97
|
+
console.log(` š Fetching project attributes for ${projectIdn}...`);
|
|
98
|
+
try {
|
|
99
|
+
const response = await getProjectAttributes(client, projectId, true); // Include hidden attributes
|
|
100
|
+
// API returns { groups: [...], attributes: [...] }
|
|
101
|
+
const attributes = response.attributes || response;
|
|
102
|
+
if (verbose)
|
|
103
|
+
console.log(` š¦ Found ${Array.isArray(attributes) ? attributes.length : 0} project attributes`);
|
|
104
|
+
if (!Array.isArray(attributes) || attributes.length === 0) {
|
|
105
|
+
if (verbose)
|
|
106
|
+
console.log(` ā¹ No project attributes found for ${projectIdn}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Create ID mapping for push operations
|
|
110
|
+
const idMapping = {};
|
|
111
|
+
// Transform attributes to match format (no ID fields)
|
|
112
|
+
const cleanAttributes = attributes.map(attr => {
|
|
113
|
+
// Store ID mapping
|
|
114
|
+
if (attr.id) {
|
|
115
|
+
idMapping[attr.idn] = attr.id;
|
|
116
|
+
}
|
|
117
|
+
// Special handling for complex JSON string values
|
|
118
|
+
let processedValue = attr.value;
|
|
119
|
+
if (typeof attr.value === 'string' && attr.value.startsWith('[{') && attr.value.endsWith('}]')) {
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(attr.value);
|
|
122
|
+
processedValue = JSON.stringify(parsed, null, 0);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
processedValue = attr.value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
idn: attr.idn,
|
|
130
|
+
value: processedValue,
|
|
131
|
+
title: attr.title || "",
|
|
132
|
+
description: attr.description || "",
|
|
133
|
+
group: attr.group || "",
|
|
134
|
+
is_hidden: attr.is_hidden,
|
|
135
|
+
possible_values: attr.possible_values || [],
|
|
136
|
+
value_type: `__ENUM_PLACEHOLDER_${attr.value_type}__`
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
const attributesYaml = {
|
|
140
|
+
attributes: cleanAttributes
|
|
141
|
+
};
|
|
142
|
+
// Configure YAML output
|
|
143
|
+
let yamlContent = yaml.dump(attributesYaml, {
|
|
144
|
+
indent: 2,
|
|
145
|
+
quotingType: '"',
|
|
146
|
+
forceQuotes: false,
|
|
147
|
+
lineWidth: 80,
|
|
148
|
+
noRefs: true,
|
|
149
|
+
sortKeys: false,
|
|
150
|
+
flowLevel: -1
|
|
151
|
+
});
|
|
152
|
+
// Post-process to fix enum format
|
|
153
|
+
yamlContent = yamlContent.replace(/__ENUM_PLACEHOLDER_(\w+)__/g, '!enum "AttributeValueTypes.$1"');
|
|
154
|
+
yamlContent = yamlContent.replace(/\\"/g, '"');
|
|
155
|
+
// Save to project directory
|
|
156
|
+
const customerDir = path.join(process.cwd(), 'newo_customers', customer.idn);
|
|
157
|
+
const projectDir = path.join(customerDir, 'projects', projectIdn);
|
|
158
|
+
await fs.ensureDir(projectDir);
|
|
159
|
+
const attributesFile = path.join(projectDir, 'attributes.yaml');
|
|
160
|
+
const attributesMapFile = path.join(customerDir, '.newo', customer.idn, `project_${projectIdn}_attributes-map.json`);
|
|
161
|
+
await writeFileSafe(attributesFile, yamlContent);
|
|
162
|
+
await fs.ensureDir(path.dirname(attributesMapFile));
|
|
163
|
+
await writeFileSafe(attributesMapFile, JSON.stringify(idMapping, null, 2));
|
|
164
|
+
if (verbose) {
|
|
165
|
+
console.log(` ā Saved project attributes to projects/${projectIdn}/attributes.yaml`);
|
|
166
|
+
console.log(` ā Saved attribute ID mapping`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (verbose)
|
|
171
|
+
console.error(` ā Failed to fetch project attributes for ${projectIdn}:`, error.message);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Pull all project attributes for a customer
|
|
176
|
+
*/
|
|
177
|
+
export async function pullAllProjectAttributes(client, customer, verbose = false) {
|
|
178
|
+
if (verbose)
|
|
179
|
+
console.log(`\nš Fetching project attributes...`);
|
|
180
|
+
try {
|
|
181
|
+
// Get all projects for this customer
|
|
182
|
+
const projects = await listProjects(client);
|
|
183
|
+
if (verbose)
|
|
184
|
+
console.log(`ā Found ${projects.length} projects\n`);
|
|
185
|
+
for (const project of projects) {
|
|
186
|
+
await saveProjectAttributes(client, customer, project.id, project.idn, verbose);
|
|
187
|
+
}
|
|
188
|
+
if (verbose)
|
|
189
|
+
console.log(`\nā
Completed project attributes sync for ${projects.length} projects\n`);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.error(`ā Failed to pull project attributes:`, error);
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Push modified project attributes for a specific project
|
|
198
|
+
*/
|
|
199
|
+
export async function pushProjectAttributes(client, customer, projectId, projectIdn, verbose = false) {
|
|
200
|
+
const customerDir = path.join(process.cwd(), 'newo_customers', customer.idn);
|
|
201
|
+
const attributesFile = path.join(customerDir, 'projects', projectIdn, 'attributes.yaml');
|
|
202
|
+
const attributesMapFile = path.join(customerDir, '.newo', customer.idn, `project_${projectIdn}_attributes-map.json`);
|
|
203
|
+
// Check if attributes file exists
|
|
204
|
+
if (!await fs.pathExists(attributesFile)) {
|
|
205
|
+
if (verbose)
|
|
206
|
+
console.log(` ā¹ No project attributes file for ${projectIdn}`);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
// Load local attributes
|
|
210
|
+
// Read as text and replace the custom !enum tags before parsing
|
|
211
|
+
let attributesContent = await fs.readFile(attributesFile, 'utf-8');
|
|
212
|
+
// Replace custom enum tags with the actual value
|
|
213
|
+
attributesContent = attributesContent.replace(/!enum "AttributeValueTypes\.(\w+)"/g, '$1');
|
|
214
|
+
const localData = yaml.load(attributesContent);
|
|
215
|
+
const localAttributes = localData.attributes || [];
|
|
216
|
+
// Load ID mapping
|
|
217
|
+
if (!await fs.pathExists(attributesMapFile)) {
|
|
218
|
+
if (verbose)
|
|
219
|
+
console.log(` ā No ID mapping found for project ${projectIdn}, skipping push`);
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
const idMapping = JSON.parse(await fs.readFile(attributesMapFile, 'utf-8'));
|
|
223
|
+
// Fetch current remote attributes for comparison
|
|
224
|
+
const remoteResponse = await getProjectAttributes(client, projectId, true);
|
|
225
|
+
const remoteAttributes = remoteResponse.attributes || [];
|
|
226
|
+
// Create map of remote attributes by IDN
|
|
227
|
+
const remoteMap = new Map();
|
|
228
|
+
remoteAttributes.forEach(attr => remoteMap.set(attr.idn, attr));
|
|
229
|
+
let updatedCount = 0;
|
|
230
|
+
// Check each local attribute for changes
|
|
231
|
+
for (const localAttr of localAttributes) {
|
|
232
|
+
const attributeId = idMapping[localAttr.idn];
|
|
233
|
+
if (!attributeId) {
|
|
234
|
+
if (verbose)
|
|
235
|
+
console.log(` ā No ID mapping for attribute ${localAttr.idn}, skipping`);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const remoteAttr = remoteMap.get(localAttr.idn);
|
|
239
|
+
if (!remoteAttr) {
|
|
240
|
+
if (verbose)
|
|
241
|
+
console.log(` ā Attribute ${localAttr.idn} not found remotely, skipping`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// Value type is already parsed (we removed !enum tags above)
|
|
245
|
+
const valueType = localAttr.value_type;
|
|
246
|
+
// Check if value changed
|
|
247
|
+
const localValue = String(localAttr.value || '');
|
|
248
|
+
const remoteValue = String(remoteAttr.value || '');
|
|
249
|
+
if (localValue !== remoteValue) {
|
|
250
|
+
if (verbose)
|
|
251
|
+
console.log(` š Updating project attribute: ${localAttr.idn}`);
|
|
252
|
+
try {
|
|
253
|
+
const attributeToUpdate = {
|
|
254
|
+
id: attributeId,
|
|
255
|
+
idn: localAttr.idn,
|
|
256
|
+
value: localAttr.value,
|
|
257
|
+
title: localAttr.title,
|
|
258
|
+
description: localAttr.description,
|
|
259
|
+
group: localAttr.group,
|
|
260
|
+
is_hidden: localAttr.is_hidden,
|
|
261
|
+
possible_values: localAttr.possible_values,
|
|
262
|
+
value_type: valueType
|
|
263
|
+
};
|
|
264
|
+
await updateProjectAttribute(client, projectId, attributeToUpdate);
|
|
265
|
+
if (verbose)
|
|
266
|
+
console.log(` ā
Updated: ${localAttr.idn} (${localAttr.title})`);
|
|
267
|
+
updatedCount++;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
const errorDetail = error.response?.data || error.message;
|
|
271
|
+
console.error(` ā Failed to update ${localAttr.idn}: ${JSON.stringify(errorDetail)}`);
|
|
272
|
+
if (verbose) {
|
|
273
|
+
console.error(` API response:`, error.response?.status, error.response?.statusText);
|
|
274
|
+
console.error(` Endpoint tried: PUT /api/v1/project/attributes/${attributeId}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (verbose) {
|
|
279
|
+
console.log(` ā No changes: ${localAttr.idn}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return updatedCount;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Push all modified project attributes for all projects
|
|
286
|
+
*/
|
|
287
|
+
export async function pushAllProjectAttributes(client, customer, projectsMap, verbose = false) {
|
|
288
|
+
if (verbose)
|
|
289
|
+
console.log(`\nš Checking project attributes for changes...`);
|
|
290
|
+
let totalUpdated = 0;
|
|
291
|
+
for (const [projectIdn, projectInfo] of Object.entries(projectsMap)) {
|
|
292
|
+
if (!projectIdn)
|
|
293
|
+
continue; // Skip empty project idn (legacy format)
|
|
294
|
+
if (verbose)
|
|
295
|
+
console.log(`\n š Project: ${projectIdn}`);
|
|
296
|
+
const updated = await pushProjectAttributes(client, customer, projectInfo.projectId, projectInfo.projectIdn || projectIdn, verbose);
|
|
297
|
+
totalUpdated += updated;
|
|
298
|
+
}
|
|
299
|
+
// Always show summary if changes were made
|
|
300
|
+
if (totalUpdated > 0) {
|
|
301
|
+
console.log(`\nā
Updated ${totalUpdated} project attribute(s)`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
if (verbose)
|
|
305
|
+
console.log(`\nā No project attribute changes to push`);
|
|
306
|
+
}
|
|
307
|
+
return totalUpdated;
|
|
308
|
+
}
|
|
90
309
|
//# sourceMappingURL=attributes.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration and connector synchronization module
|
|
3
|
+
* Handles pull/push of integrations and connectors to/from NEWO platform
|
|
4
|
+
*/
|
|
5
|
+
import type { AxiosInstance } from 'axios';
|
|
6
|
+
import type { IntegrationMetadata, ConnectorMetadata } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Pull all integrations and connectors from NEWO platform
|
|
9
|
+
*/
|
|
10
|
+
export declare function pullIntegrations(client: AxiosInstance, customerDir: string, verbose?: boolean): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Push integration changes from local files to NEWO platform
|
|
13
|
+
*/
|
|
14
|
+
export declare function pushIntegrations(client: AxiosInstance, customerDir: string, verbose?: boolean): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* List all local integrations from file system
|
|
17
|
+
*/
|
|
18
|
+
export declare function listLocalIntegrations(customerDir: string): Promise<IntegrationMetadata[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Get connector details from local file
|
|
21
|
+
*/
|
|
22
|
+
export declare function getLocalConnector(customerDir: string, integrationIdn: string, connectorIdn: string): Promise<ConnectorMetadata | null>;
|
|
23
|
+
//# sourceMappingURL=integrations.d.ts.map
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration and connector synchronization module
|
|
3
|
+
* Handles pull/push of integrations and connectors to/from NEWO platform
|
|
4
|
+
*/
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
import { listIntegrations, listConnectors, getIntegrationSettings, createConnector, updateConnector, deleteConnector, listOutgoingWebhooks, listIncomingWebhooks } from '../api.js';
|
|
9
|
+
/**
|
|
10
|
+
* Pull all integrations and connectors from NEWO platform
|
|
11
|
+
*/
|
|
12
|
+
export async function pullIntegrations(client, customerDir, verbose = false) {
|
|
13
|
+
if (verbose)
|
|
14
|
+
console.log('\nš¦ Pulling integrations from NEWO platform...\n');
|
|
15
|
+
// Create integrations directory
|
|
16
|
+
const integrationsDir = path.join(customerDir, 'integrations');
|
|
17
|
+
await fs.ensureDir(integrationsDir);
|
|
18
|
+
// Fetch all integrations
|
|
19
|
+
const integrations = await listIntegrations(client);
|
|
20
|
+
if (verbose)
|
|
21
|
+
console.log(`ā Found ${integrations.length} integrations`);
|
|
22
|
+
const integrationsMetadata = [];
|
|
23
|
+
// Process each integration
|
|
24
|
+
for (const integration of integrations) {
|
|
25
|
+
if (verbose)
|
|
26
|
+
console.log(`\n š¦ Processing: ${integration.title} (${integration.idn})`);
|
|
27
|
+
// Add to metadata list
|
|
28
|
+
integrationsMetadata.push({
|
|
29
|
+
id: integration.id,
|
|
30
|
+
idn: integration.idn,
|
|
31
|
+
title: integration.title,
|
|
32
|
+
description: integration.description,
|
|
33
|
+
channel: integration.channel,
|
|
34
|
+
is_disabled: integration.is_disabled
|
|
35
|
+
});
|
|
36
|
+
// Create integration directory
|
|
37
|
+
const integrationDir = path.join(integrationsDir, integration.idn);
|
|
38
|
+
await fs.ensureDir(integrationDir);
|
|
39
|
+
// Fetch integration settings
|
|
40
|
+
let integrationSettings = [];
|
|
41
|
+
try {
|
|
42
|
+
integrationSettings = await getIntegrationSettings(client, integration.id);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// Settings endpoint may not be available for all integrations
|
|
46
|
+
if (verbose && error.response?.status !== 404) {
|
|
47
|
+
console.log(` ā Could not fetch settings: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Save combined integration file (metadata + settings)
|
|
51
|
+
const integrationFile = path.join(integrationDir, `${integration.idn}.yaml`);
|
|
52
|
+
const integrationData = {
|
|
53
|
+
id: integration.id,
|
|
54
|
+
idn: integration.idn,
|
|
55
|
+
title: integration.title,
|
|
56
|
+
description: integration.description,
|
|
57
|
+
channel: integration.channel,
|
|
58
|
+
is_disabled: integration.is_disabled
|
|
59
|
+
};
|
|
60
|
+
// Add settings array if any settings exist
|
|
61
|
+
if (integrationSettings.length > 0) {
|
|
62
|
+
integrationData.settings = integrationSettings;
|
|
63
|
+
}
|
|
64
|
+
await fs.writeFile(integrationFile, yaml.dump(integrationData, { lineWidth: -1 }));
|
|
65
|
+
if (verbose)
|
|
66
|
+
console.log(` ā Saved integration ā ${integration.idn}.yaml (${integrationSettings.length} settings)`);
|
|
67
|
+
// Fetch and save connectors
|
|
68
|
+
const connectors = await listConnectors(client, integration.id);
|
|
69
|
+
if (verbose)
|
|
70
|
+
console.log(` Connectors: ${connectors.length} found`);
|
|
71
|
+
if (connectors.length > 0) {
|
|
72
|
+
const connectorsDir = path.join(integrationDir, 'connectors');
|
|
73
|
+
await fs.ensureDir(connectorsDir);
|
|
74
|
+
for (const connector of connectors) {
|
|
75
|
+
const connectorMetadata = {
|
|
76
|
+
id: connector.id,
|
|
77
|
+
connector_idn: connector.connector_idn,
|
|
78
|
+
title: connector.title,
|
|
79
|
+
status: connector.status,
|
|
80
|
+
integration_idn: integration.idn,
|
|
81
|
+
settings: connector.settings
|
|
82
|
+
};
|
|
83
|
+
// Create subdirectory for this connector
|
|
84
|
+
const connectorDir = path.join(connectorsDir, connector.connector_idn);
|
|
85
|
+
await fs.ensureDir(connectorDir);
|
|
86
|
+
// Save connector YAML file inside its subdirectory
|
|
87
|
+
const connectorFile = path.join(connectorDir, `${connector.connector_idn}.yaml`);
|
|
88
|
+
await fs.writeFile(connectorFile, yaml.dump(connectorMetadata, { lineWidth: -1 }));
|
|
89
|
+
if (verbose)
|
|
90
|
+
console.log(` ā Saved: ${connector.title} ā connectors/${connector.connector_idn}/${connector.connector_idn}.yaml`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Fetch and save webhooks (for API integration connectors only)
|
|
95
|
+
if (verbose)
|
|
96
|
+
console.log(`\nš” Fetching webhooks...`);
|
|
97
|
+
try {
|
|
98
|
+
const outgoingWebhooks = await listOutgoingWebhooks(client);
|
|
99
|
+
const incomingWebhooks = await listIncomingWebhooks(client);
|
|
100
|
+
if (verbose)
|
|
101
|
+
console.log(`ā Found ${outgoingWebhooks.length} outgoing webhooks`);
|
|
102
|
+
if (verbose)
|
|
103
|
+
console.log(`ā Found ${incomingWebhooks.length} incoming webhooks`);
|
|
104
|
+
// Group webhooks by connector_idn
|
|
105
|
+
const outgoingByConnector = new Map();
|
|
106
|
+
const incomingByConnector = new Map();
|
|
107
|
+
outgoingWebhooks.forEach(webhook => {
|
|
108
|
+
if (!outgoingByConnector.has(webhook.connector_idn)) {
|
|
109
|
+
outgoingByConnector.set(webhook.connector_idn, []);
|
|
110
|
+
}
|
|
111
|
+
outgoingByConnector.get(webhook.connector_idn).push(webhook);
|
|
112
|
+
});
|
|
113
|
+
incomingWebhooks.forEach(webhook => {
|
|
114
|
+
if (!incomingByConnector.has(webhook.connector_idn)) {
|
|
115
|
+
incomingByConnector.set(webhook.connector_idn, []);
|
|
116
|
+
}
|
|
117
|
+
incomingByConnector.get(webhook.connector_idn).push(webhook);
|
|
118
|
+
});
|
|
119
|
+
// Save webhooks to appropriate connector directories
|
|
120
|
+
for (const integration of integrations) {
|
|
121
|
+
const integrationDir = path.join(integrationsDir, integration.idn);
|
|
122
|
+
const connectorsDir = path.join(integrationDir, 'connectors');
|
|
123
|
+
if (await fs.pathExists(connectorsDir)) {
|
|
124
|
+
const connectors = await listConnectors(client, integration.id);
|
|
125
|
+
for (const connector of connectors) {
|
|
126
|
+
const connectorWebhooksDir = path.join(connectorsDir, connector.connector_idn, 'webhooks');
|
|
127
|
+
const outgoing = outgoingByConnector.get(connector.connector_idn) || [];
|
|
128
|
+
const incoming = incomingByConnector.get(connector.connector_idn) || [];
|
|
129
|
+
if (outgoing.length > 0 || incoming.length > 0) {
|
|
130
|
+
await fs.ensureDir(connectorWebhooksDir);
|
|
131
|
+
if (outgoing.length > 0) {
|
|
132
|
+
const outgoingFile = path.join(connectorWebhooksDir, 'outgoing.yaml');
|
|
133
|
+
await fs.writeFile(outgoingFile, yaml.dump({ webhooks: outgoing }, { lineWidth: -1 }));
|
|
134
|
+
if (verbose)
|
|
135
|
+
console.log(` ā Saved: ${outgoing.length} outgoing webhooks ā ${connector.connector_idn}/webhooks/outgoing.yaml`);
|
|
136
|
+
}
|
|
137
|
+
if (incoming.length > 0) {
|
|
138
|
+
const incomingFile = path.join(connectorWebhooksDir, 'incoming.yaml');
|
|
139
|
+
await fs.writeFile(incomingFile, yaml.dump({ webhooks: incoming }, { lineWidth: -1 }));
|
|
140
|
+
if (verbose)
|
|
141
|
+
console.log(` ā Saved: ${incoming.length} incoming webhooks ā ${connector.connector_idn}/webhooks/incoming.yaml`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (verbose)
|
|
150
|
+
console.log(`ā Could not fetch webhooks: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
// Save master integrations list
|
|
153
|
+
const integrationsData = { integrations: integrationsMetadata };
|
|
154
|
+
const integrationsFile = path.join(integrationsDir, 'integrations.yaml');
|
|
155
|
+
await fs.writeFile(integrationsFile, yaml.dump(integrationsData, { lineWidth: -1 }));
|
|
156
|
+
if (verbose)
|
|
157
|
+
console.log(`\nā
Saved ${integrations.length} integrations to integrations/integrations.yaml\n`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Push integration changes from local files to NEWO platform
|
|
161
|
+
*/
|
|
162
|
+
export async function pushIntegrations(client, customerDir, verbose = false) {
|
|
163
|
+
if (verbose)
|
|
164
|
+
console.log('\nš¤ Pushing integration changes to NEWO platform...\n');
|
|
165
|
+
const integrationsDir = path.join(customerDir, 'integrations');
|
|
166
|
+
// Check if integrations directory exists
|
|
167
|
+
if (!await fs.pathExists(integrationsDir)) {
|
|
168
|
+
if (verbose)
|
|
169
|
+
console.log('ā No integrations directory found. Run pull-integrations first.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Load remote integrations for ID mapping
|
|
173
|
+
const remoteIntegrations = await listIntegrations(client);
|
|
174
|
+
const integrationMap = new Map(); // idn -> id
|
|
175
|
+
remoteIntegrations.forEach(int => integrationMap.set(int.idn, int.id));
|
|
176
|
+
let updatedCount = 0;
|
|
177
|
+
let createdCount = 0;
|
|
178
|
+
let deletedCount = 0;
|
|
179
|
+
// Read integrations directory
|
|
180
|
+
const integrationFolders = await fs.readdir(integrationsDir);
|
|
181
|
+
for (const folder of integrationFolders) {
|
|
182
|
+
if (folder === 'integrations.yaml')
|
|
183
|
+
continue; // Skip master file
|
|
184
|
+
const integrationDir = path.join(integrationsDir, folder);
|
|
185
|
+
const stat = await fs.stat(integrationDir);
|
|
186
|
+
if (!stat.isDirectory())
|
|
187
|
+
continue;
|
|
188
|
+
const integrationIdn = folder;
|
|
189
|
+
const integrationId = integrationMap.get(integrationIdn);
|
|
190
|
+
if (!integrationId) {
|
|
191
|
+
if (verbose)
|
|
192
|
+
console.log(`ā Integration ${integrationIdn} not found on platform, skipping...`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (verbose)
|
|
196
|
+
console.log(`\n š¦ Processing: ${integrationIdn}`);
|
|
197
|
+
// Process connectors
|
|
198
|
+
const connectorsDir = path.join(integrationDir, 'connectors');
|
|
199
|
+
if (await fs.pathExists(connectorsDir)) {
|
|
200
|
+
// Load remote connectors for comparison
|
|
201
|
+
const remoteConnectors = await listConnectors(client, integrationId);
|
|
202
|
+
const remoteConnectorMap = new Map();
|
|
203
|
+
remoteConnectors.forEach(conn => remoteConnectorMap.set(conn.connector_idn, conn));
|
|
204
|
+
// Read connector subdirectories
|
|
205
|
+
const connectorDirs = await fs.readdir(connectorsDir);
|
|
206
|
+
const localConnectorIdns = new Set();
|
|
207
|
+
for (const connectorDirName of connectorDirs) {
|
|
208
|
+
const connectorPath = path.join(connectorsDir, connectorDirName);
|
|
209
|
+
const stat = await fs.stat(connectorPath);
|
|
210
|
+
if (!stat.isDirectory())
|
|
211
|
+
continue; // Skip non-directories
|
|
212
|
+
// Read connector YAML file from within the subdirectory
|
|
213
|
+
const connectorFile = path.join(connectorPath, `${connectorDirName}.yaml`);
|
|
214
|
+
if (!await fs.pathExists(connectorFile)) {
|
|
215
|
+
if (verbose)
|
|
216
|
+
console.log(` ā No YAML file found in ${connectorDirName}/, skipping...`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const connectorData = yaml.load(await fs.readFile(connectorFile, 'utf-8'));
|
|
220
|
+
localConnectorIdns.add(connectorData.connector_idn);
|
|
221
|
+
const remoteConnector = remoteConnectorMap.get(connectorData.connector_idn);
|
|
222
|
+
if (!remoteConnector) {
|
|
223
|
+
// Create new connector
|
|
224
|
+
if (verbose)
|
|
225
|
+
console.log(` ā Creating connector: ${connectorData.title}`);
|
|
226
|
+
try {
|
|
227
|
+
await createConnector(client, integrationId, {
|
|
228
|
+
title: connectorData.title,
|
|
229
|
+
connector_idn: connectorData.connector_idn,
|
|
230
|
+
integration_idn: integrationIdn,
|
|
231
|
+
settings: connectorData.settings
|
|
232
|
+
});
|
|
233
|
+
createdCount++;
|
|
234
|
+
if (verbose)
|
|
235
|
+
console.log(` ā
Created: ${connectorData.title}`);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error(` ā Failed to create connector: ${error.message}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Check if connector needs update
|
|
243
|
+
const needsUpdate = hasConnectorChanged(remoteConnector, connectorData);
|
|
244
|
+
if (needsUpdate) {
|
|
245
|
+
if (verbose)
|
|
246
|
+
console.log(` š Updating connector: ${connectorData.title}`);
|
|
247
|
+
try {
|
|
248
|
+
await updateConnector(client, remoteConnector.id, {
|
|
249
|
+
title: connectorData.title,
|
|
250
|
+
status: connectorData.status,
|
|
251
|
+
settings: connectorData.settings
|
|
252
|
+
});
|
|
253
|
+
updatedCount++;
|
|
254
|
+
if (verbose)
|
|
255
|
+
console.log(` ā
Updated: ${connectorData.title}`);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(` ā Failed to update connector: ${error.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
if (verbose)
|
|
263
|
+
console.log(` ā No changes: ${connectorData.title}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Delete connectors that exist remotely but not locally
|
|
268
|
+
for (const [connectorIdn, remoteConnector] of remoteConnectorMap) {
|
|
269
|
+
if (!localConnectorIdns.has(connectorIdn)) {
|
|
270
|
+
if (verbose)
|
|
271
|
+
console.log(` šļø Deleting connector: ${remoteConnector.title}`);
|
|
272
|
+
try {
|
|
273
|
+
await deleteConnector(client, remoteConnector.id);
|
|
274
|
+
deletedCount++;
|
|
275
|
+
if (verbose)
|
|
276
|
+
console.log(` ā
Deleted: ${remoteConnector.title}`);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error(` ā Failed to delete connector: ${error.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Always show summary if changes were made, not just in verbose mode
|
|
286
|
+
if (createdCount > 0 || updatedCount > 0 || deletedCount > 0) {
|
|
287
|
+
console.log(`\nā
Integration push completed:`);
|
|
288
|
+
console.log(` Created: ${createdCount} connector(s)`);
|
|
289
|
+
console.log(` Updated: ${updatedCount} connector(s)`);
|
|
290
|
+
console.log(` Deleted: ${deletedCount} connector(s)`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log(`\nā No connector changes to push`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Check if connector has changed compared to remote version
|
|
298
|
+
*/
|
|
299
|
+
function hasConnectorChanged(remote, local) {
|
|
300
|
+
// Check title
|
|
301
|
+
if (remote.title !== local.title)
|
|
302
|
+
return true;
|
|
303
|
+
// Check status
|
|
304
|
+
if (remote.status !== local.status)
|
|
305
|
+
return true;
|
|
306
|
+
// Check settings
|
|
307
|
+
if (remote.settings.length !== local.settings.length)
|
|
308
|
+
return true;
|
|
309
|
+
// Compare each setting
|
|
310
|
+
const remoteSettingsMap = new Map();
|
|
311
|
+
remote.settings.forEach(s => remoteSettingsMap.set(s.idn, s.value));
|
|
312
|
+
for (const localSetting of local.settings) {
|
|
313
|
+
const remoteValue = remoteSettingsMap.get(localSetting.idn);
|
|
314
|
+
if (remoteValue !== localSetting.value)
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* List all local integrations from file system
|
|
321
|
+
*/
|
|
322
|
+
export async function listLocalIntegrations(customerDir) {
|
|
323
|
+
const integrationsFile = path.join(customerDir, 'integrations', 'integrations.yaml');
|
|
324
|
+
if (!await fs.pathExists(integrationsFile)) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const data = yaml.load(await fs.readFile(integrationsFile, 'utf-8'));
|
|
328
|
+
return data.integrations;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get connector details from local file
|
|
332
|
+
*/
|
|
333
|
+
export async function getLocalConnector(customerDir, integrationIdn, connectorIdn) {
|
|
334
|
+
const connectorFile = path.join(customerDir, 'integrations', integrationIdn, 'connectors', connectorIdn, `${connectorIdn}.yaml`);
|
|
335
|
+
if (!await fs.pathExists(connectorFile)) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
return yaml.load(await fs.readFile(connectorFile, 'utf-8'));
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=integrations.js.map
|