newo 3.4.0 → 3.4.2
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 +16 -0
- package/dist/api.d.ts +3 -1
- package/dist/api.js +49 -1
- package/dist/application/migration/MigrationEngine.d.ts +141 -0
- package/dist/application/migration/MigrationEngine.js +322 -0
- package/dist/application/migration/index.d.ts +5 -0
- package/dist/application/migration/index.js +5 -0
- package/dist/application/sync/SyncEngine.d.ts +134 -0
- package/dist/application/sync/SyncEngine.js +335 -0
- package/dist/application/sync/index.d.ts +5 -0
- package/dist/application/sync/index.js +5 -0
- package/dist/cli/commands/create-attribute.js +1 -1
- package/dist/cli/commands/create-customer.d.ts +3 -0
- package/dist/cli/commands/create-customer.js +159 -0
- package/dist/cli/commands/diff.d.ts +6 -0
- package/dist/cli/commands/diff.js +288 -0
- package/dist/cli/commands/help.js +63 -3
- package/dist/cli/commands/logs.d.ts +18 -0
- package/dist/cli/commands/logs.js +283 -0
- package/dist/cli/commands/pull.js +114 -10
- package/dist/cli/commands/push.js +122 -12
- package/dist/cli/commands/update-attribute.d.ts +3 -0
- package/dist/cli/commands/update-attribute.js +78 -0
- package/dist/cli/commands/watch.d.ts +6 -0
- package/dist/cli/commands/watch.js +195 -0
- package/dist/cli-new/bootstrap.d.ts +74 -0
- package/dist/cli-new/bootstrap.js +154 -0
- package/dist/cli-new/di/Container.d.ts +64 -0
- package/dist/cli-new/di/Container.js +122 -0
- package/dist/cli-new/di/tokens.d.ts +77 -0
- package/dist/cli-new/di/tokens.js +76 -0
- package/dist/cli.js +20 -0
- package/dist/domain/resources/common/types.d.ts +71 -0
- package/dist/domain/resources/common/types.js +42 -0
- package/dist/domain/strategies/sync/AkbSyncStrategy.d.ts +63 -0
- package/dist/domain/strategies/sync/AkbSyncStrategy.js +274 -0
- package/dist/domain/strategies/sync/AttributeSyncStrategy.d.ts +87 -0
- package/dist/domain/strategies/sync/AttributeSyncStrategy.js +378 -0
- package/dist/domain/strategies/sync/ConversationSyncStrategy.d.ts +61 -0
- package/dist/domain/strategies/sync/ConversationSyncStrategy.js +232 -0
- package/dist/domain/strategies/sync/ISyncStrategy.d.ts +149 -0
- package/dist/domain/strategies/sync/ISyncStrategy.js +24 -0
- package/dist/domain/strategies/sync/IntegrationSyncStrategy.d.ts +68 -0
- package/dist/domain/strategies/sync/IntegrationSyncStrategy.js +413 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +111 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.js +523 -0
- package/dist/domain/strategies/sync/index.d.ts +13 -0
- package/dist/domain/strategies/sync/index.js +19 -0
- package/dist/sync/migrate.js +99 -23
- package/dist/types.d.ts +124 -0
- package/package.json +3 -1
- package/src/api.ts +53 -2
- package/src/application/migration/MigrationEngine.ts +492 -0
- package/src/application/migration/index.ts +5 -0
- package/src/application/sync/SyncEngine.ts +467 -0
- package/src/application/sync/index.ts +5 -0
- package/src/cli/commands/create-attribute.ts +1 -1
- package/src/cli/commands/create-customer.ts +185 -0
- package/src/cli/commands/diff.ts +360 -0
- package/src/cli/commands/help.ts +63 -3
- package/src/cli/commands/logs.ts +329 -0
- package/src/cli/commands/pull.ts +128 -11
- package/src/cli/commands/push.ts +131 -13
- package/src/cli/commands/update-attribute.ts +82 -0
- package/src/cli/commands/watch.ts +227 -0
- package/src/cli-new/bootstrap.ts +252 -0
- package/src/cli-new/di/Container.ts +152 -0
- package/src/cli-new/di/tokens.ts +105 -0
- package/src/cli.ts +25 -0
- package/src/domain/resources/common/types.ts +106 -0
- package/src/domain/strategies/sync/AkbSyncStrategy.ts +358 -0
- package/src/domain/strategies/sync/AttributeSyncStrategy.ts +508 -0
- package/src/domain/strategies/sync/ConversationSyncStrategy.ts +299 -0
- package/src/domain/strategies/sync/ISyncStrategy.ts +182 -0
- package/src/domain/strategies/sync/IntegrationSyncStrategy.ts +522 -0
- package/src/domain/strategies/sync/ProjectSyncStrategy.ts +747 -0
- package/src/domain/strategies/sync/index.ts +46 -0
- package/src/sync/migrate.ts +103 -24
- package/src/types.ts +135 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncEngine - Core synchronization orchestrator
|
|
3
|
+
*
|
|
4
|
+
* This is the central engine that coordinates sync operations across all resource types.
|
|
5
|
+
* It uses the Strategy pattern to handle different resources uniformly.
|
|
6
|
+
*
|
|
7
|
+
* Key benefits:
|
|
8
|
+
* - One engine handles projects, integrations, AKB, attributes, conversations
|
|
9
|
+
* - Adding new resource = implement one strategy class
|
|
10
|
+
* - No duplicate pull/push logic
|
|
11
|
+
* - Easy to test (mock strategies)
|
|
12
|
+
*/
|
|
13
|
+
import type { ISyncStrategy, PullOptions, PullResult, PushResult, StatusSummary, ValidationResult } from '../../domain/strategies/sync/ISyncStrategy.js';
|
|
14
|
+
import type { CustomerConfig, ILogger } from '../../domain/resources/common/types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Combined pull result from all strategies
|
|
17
|
+
*/
|
|
18
|
+
export interface SyncPullResult {
|
|
19
|
+
customer: string;
|
|
20
|
+
resources: Array<{
|
|
21
|
+
resourceType: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
result: PullResult;
|
|
24
|
+
}>;
|
|
25
|
+
totalItems: number;
|
|
26
|
+
errors: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Combined push result from all strategies
|
|
30
|
+
*/
|
|
31
|
+
export interface SyncPushResult {
|
|
32
|
+
customer: string;
|
|
33
|
+
resources: Array<{
|
|
34
|
+
resourceType: string;
|
|
35
|
+
displayName: string;
|
|
36
|
+
result: PushResult;
|
|
37
|
+
}>;
|
|
38
|
+
totalCreated: number;
|
|
39
|
+
totalUpdated: number;
|
|
40
|
+
totalDeleted: number;
|
|
41
|
+
errors: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Status report for all resources
|
|
45
|
+
*/
|
|
46
|
+
export interface StatusReport {
|
|
47
|
+
customer: string;
|
|
48
|
+
resources: StatusSummary[];
|
|
49
|
+
totalChanges: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Sync error with context
|
|
53
|
+
*/
|
|
54
|
+
export declare class SyncError extends Error {
|
|
55
|
+
resourceType: string;
|
|
56
|
+
cause?: Error | undefined;
|
|
57
|
+
constructor(message: string, resourceType: string, cause?: Error | undefined);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validation error with details
|
|
61
|
+
*/
|
|
62
|
+
export declare class ValidationError extends Error {
|
|
63
|
+
results: ValidationResult[];
|
|
64
|
+
constructor(message: string, results: ValidationResult[]);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* SyncEngine Options
|
|
68
|
+
*/
|
|
69
|
+
export interface SyncEngineOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Stop on first error instead of continuing
|
|
72
|
+
*/
|
|
73
|
+
stopOnError?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Run strategies in parallel where possible
|
|
76
|
+
*/
|
|
77
|
+
parallel?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* SyncEngine - Generic synchronization orchestrator
|
|
81
|
+
*
|
|
82
|
+
* Orchestrates pull/push/status operations across all registered strategies.
|
|
83
|
+
*/
|
|
84
|
+
export declare class SyncEngine {
|
|
85
|
+
private logger;
|
|
86
|
+
private options;
|
|
87
|
+
private strategies;
|
|
88
|
+
constructor(strategies: ISyncStrategy[], logger: ILogger, options?: SyncEngineOptions);
|
|
89
|
+
/**
|
|
90
|
+
* Register a new strategy
|
|
91
|
+
*/
|
|
92
|
+
registerStrategy(strategy: ISyncStrategy): void;
|
|
93
|
+
/**
|
|
94
|
+
* Get a specific strategy by resource type
|
|
95
|
+
*/
|
|
96
|
+
getStrategy(resourceType: string): ISyncStrategy | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Get all registered strategies
|
|
99
|
+
*/
|
|
100
|
+
getStrategies(): ISyncStrategy[];
|
|
101
|
+
/**
|
|
102
|
+
* Pull ALL resources using registered strategies
|
|
103
|
+
*/
|
|
104
|
+
pullAll(customer: CustomerConfig, options?: PullOptions): Promise<SyncPullResult>;
|
|
105
|
+
/**
|
|
106
|
+
* Pull specific resource types
|
|
107
|
+
*/
|
|
108
|
+
pullSelected(customer: CustomerConfig, resourceTypes: string[], options?: PullOptions): Promise<SyncPullResult>;
|
|
109
|
+
/**
|
|
110
|
+
* Push ALL changed resources using registered strategies
|
|
111
|
+
*/
|
|
112
|
+
pushAll(customer: CustomerConfig): Promise<SyncPushResult>;
|
|
113
|
+
/**
|
|
114
|
+
* Push specific resource types
|
|
115
|
+
*/
|
|
116
|
+
pushSelected(customer: CustomerConfig, resourceTypes: string[]): Promise<SyncPushResult>;
|
|
117
|
+
/**
|
|
118
|
+
* Get status for ALL resources
|
|
119
|
+
*/
|
|
120
|
+
getStatus(customer: CustomerConfig): Promise<StatusReport>;
|
|
121
|
+
/**
|
|
122
|
+
* Get status for specific resource types
|
|
123
|
+
*/
|
|
124
|
+
getStatusSelected(customer: CustomerConfig, resourceTypes: string[]): Promise<StatusReport>;
|
|
125
|
+
/**
|
|
126
|
+
* Helper to execute pull with a single strategy
|
|
127
|
+
*/
|
|
128
|
+
private pullWithStrategy;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Factory function for creating SyncEngine with default strategies
|
|
132
|
+
*/
|
|
133
|
+
export declare function createSyncEngine(strategies: ISyncStrategy[], logger: ILogger, options?: SyncEngineOptions): SyncEngine;
|
|
134
|
+
//# sourceMappingURL=SyncEngine.d.ts.map
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncEngine - Core synchronization orchestrator
|
|
3
|
+
*
|
|
4
|
+
* This is the central engine that coordinates sync operations across all resource types.
|
|
5
|
+
* It uses the Strategy pattern to handle different resources uniformly.
|
|
6
|
+
*
|
|
7
|
+
* Key benefits:
|
|
8
|
+
* - One engine handles projects, integrations, AKB, attributes, conversations
|
|
9
|
+
* - Adding new resource = implement one strategy class
|
|
10
|
+
* - No duplicate pull/push logic
|
|
11
|
+
* - Easy to test (mock strategies)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Sync error with context
|
|
15
|
+
*/
|
|
16
|
+
export class SyncError extends Error {
|
|
17
|
+
resourceType;
|
|
18
|
+
cause;
|
|
19
|
+
constructor(message, resourceType, cause) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.resourceType = resourceType;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
this.name = 'SyncError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validation error with details
|
|
28
|
+
*/
|
|
29
|
+
export class ValidationError extends Error {
|
|
30
|
+
results;
|
|
31
|
+
constructor(message, results) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.results = results;
|
|
34
|
+
this.name = 'ValidationError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* SyncEngine - Generic synchronization orchestrator
|
|
39
|
+
*
|
|
40
|
+
* Orchestrates pull/push/status operations across all registered strategies.
|
|
41
|
+
*/
|
|
42
|
+
export class SyncEngine {
|
|
43
|
+
logger;
|
|
44
|
+
options;
|
|
45
|
+
strategies = new Map();
|
|
46
|
+
constructor(strategies, logger, options = {}) {
|
|
47
|
+
this.logger = logger;
|
|
48
|
+
this.options = options;
|
|
49
|
+
for (const strategy of strategies) {
|
|
50
|
+
this.strategies.set(strategy.resourceType, strategy);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register a new strategy
|
|
55
|
+
*/
|
|
56
|
+
registerStrategy(strategy) {
|
|
57
|
+
this.strategies.set(strategy.resourceType, strategy);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get a specific strategy by resource type
|
|
61
|
+
*/
|
|
62
|
+
getStrategy(resourceType) {
|
|
63
|
+
return this.strategies.get(resourceType);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get all registered strategies
|
|
67
|
+
*/
|
|
68
|
+
getStrategies() {
|
|
69
|
+
return Array.from(this.strategies.values());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pull ALL resources using registered strategies
|
|
73
|
+
*/
|
|
74
|
+
async pullAll(customer, options = {}) {
|
|
75
|
+
this.logger.info(`📥 Pulling all resources for customer: ${customer.idn}`);
|
|
76
|
+
const result = {
|
|
77
|
+
customer: customer.idn,
|
|
78
|
+
resources: [],
|
|
79
|
+
totalItems: 0,
|
|
80
|
+
errors: []
|
|
81
|
+
};
|
|
82
|
+
const strategies = Array.from(this.strategies.values());
|
|
83
|
+
if (this.options.parallel) {
|
|
84
|
+
// Parallel execution
|
|
85
|
+
const pullPromises = strategies.map(async (strategy) => {
|
|
86
|
+
try {
|
|
87
|
+
return await this.pullWithStrategy(strategy, customer, options);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = `Failed to pull ${strategy.displayName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
91
|
+
if (this.options.stopOnError) {
|
|
92
|
+
throw new SyncError(message, strategy.resourceType, error instanceof Error ? error : undefined);
|
|
93
|
+
}
|
|
94
|
+
result.errors.push(message);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
const pullResults = await Promise.all(pullPromises);
|
|
99
|
+
for (const pullResult of pullResults) {
|
|
100
|
+
if (pullResult) {
|
|
101
|
+
result.resources.push(pullResult);
|
|
102
|
+
result.totalItems += pullResult.result.count;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Sequential execution
|
|
108
|
+
for (const strategy of strategies) {
|
|
109
|
+
this.logger.info(` 📦 Pulling ${strategy.displayName}...`);
|
|
110
|
+
try {
|
|
111
|
+
const pullResult = await this.pullWithStrategy(strategy, customer, options);
|
|
112
|
+
result.resources.push(pullResult);
|
|
113
|
+
result.totalItems += pullResult.result.count;
|
|
114
|
+
this.logger.info(` ✅ Pulled ${pullResult.result.count} ${strategy.displayName}`);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
const message = `Failed to pull ${strategy.displayName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
118
|
+
this.logger.error(message, error);
|
|
119
|
+
if (this.options.stopOnError) {
|
|
120
|
+
throw new SyncError(message, strategy.resourceType, error instanceof Error ? error : undefined);
|
|
121
|
+
}
|
|
122
|
+
result.errors.push(message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
this.logger.info(`✅ Pull completed: ${result.totalItems} items from ${result.resources.length} resource types`);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Pull specific resource types
|
|
131
|
+
*/
|
|
132
|
+
async pullSelected(customer, resourceTypes, options = {}) {
|
|
133
|
+
this.logger.info(`📥 Pulling selected resources for customer: ${customer.idn}`);
|
|
134
|
+
const result = {
|
|
135
|
+
customer: customer.idn,
|
|
136
|
+
resources: [],
|
|
137
|
+
totalItems: 0,
|
|
138
|
+
errors: []
|
|
139
|
+
};
|
|
140
|
+
for (const resourceType of resourceTypes) {
|
|
141
|
+
const strategy = this.strategies.get(resourceType);
|
|
142
|
+
if (!strategy) {
|
|
143
|
+
result.errors.push(`Unknown resource type: ${resourceType}`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
this.logger.info(` 📦 Pulling ${strategy.displayName}...`);
|
|
147
|
+
try {
|
|
148
|
+
const pullResult = await this.pullWithStrategy(strategy, customer, options);
|
|
149
|
+
result.resources.push(pullResult);
|
|
150
|
+
result.totalItems += pullResult.result.count;
|
|
151
|
+
this.logger.info(` ✅ Pulled ${pullResult.result.count} ${strategy.displayName}`);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const message = `Failed to pull ${strategy.displayName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
155
|
+
this.logger.error(message, error);
|
|
156
|
+
if (this.options.stopOnError) {
|
|
157
|
+
throw new SyncError(message, strategy.resourceType, error instanceof Error ? error : undefined);
|
|
158
|
+
}
|
|
159
|
+
result.errors.push(message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Push ALL changed resources using registered strategies
|
|
166
|
+
*/
|
|
167
|
+
async pushAll(customer) {
|
|
168
|
+
this.logger.info(`📤 Pushing changes for customer: ${customer.idn}`);
|
|
169
|
+
const result = {
|
|
170
|
+
customer: customer.idn,
|
|
171
|
+
resources: [],
|
|
172
|
+
totalCreated: 0,
|
|
173
|
+
totalUpdated: 0,
|
|
174
|
+
totalDeleted: 0,
|
|
175
|
+
errors: []
|
|
176
|
+
};
|
|
177
|
+
for (const strategy of this.strategies.values()) {
|
|
178
|
+
this.logger.info(` 🔍 Checking changes for ${strategy.displayName}...`);
|
|
179
|
+
try {
|
|
180
|
+
const changes = await strategy.getChanges(customer);
|
|
181
|
+
if (changes.length === 0) {
|
|
182
|
+
this.logger.verbose(` No changes for ${strategy.displayName}`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
this.logger.info(` Found ${changes.length} changes in ${strategy.displayName}`);
|
|
186
|
+
// Validate before push
|
|
187
|
+
const items = changes.map(c => c.item);
|
|
188
|
+
const validation = await strategy.validate(customer, items);
|
|
189
|
+
if (!validation.valid) {
|
|
190
|
+
const errorMessages = validation.errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
191
|
+
throw new ValidationError(`Validation failed: ${errorMessages}`, [validation]);
|
|
192
|
+
}
|
|
193
|
+
// Push changes
|
|
194
|
+
const pushResult = await strategy.push(customer, changes);
|
|
195
|
+
result.resources.push({
|
|
196
|
+
resourceType: strategy.resourceType,
|
|
197
|
+
displayName: strategy.displayName,
|
|
198
|
+
result: pushResult
|
|
199
|
+
});
|
|
200
|
+
result.totalCreated += pushResult.created;
|
|
201
|
+
result.totalUpdated += pushResult.updated;
|
|
202
|
+
result.totalDeleted += pushResult.deleted;
|
|
203
|
+
result.errors.push(...pushResult.errors);
|
|
204
|
+
this.logger.info(` ✅ Pushed: ${pushResult.created} created, ${pushResult.updated} updated, ${pushResult.deleted} deleted`);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const message = `Failed to push ${strategy.displayName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
208
|
+
this.logger.error(message, error);
|
|
209
|
+
if (this.options.stopOnError) {
|
|
210
|
+
throw new SyncError(message, strategy.resourceType, error instanceof Error ? error : undefined);
|
|
211
|
+
}
|
|
212
|
+
result.errors.push(message);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.logger.info(`✅ Push completed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Push specific resource types
|
|
220
|
+
*/
|
|
221
|
+
async pushSelected(customer, resourceTypes) {
|
|
222
|
+
this.logger.info(`📤 Pushing selected resources for customer: ${customer.idn}`);
|
|
223
|
+
const result = {
|
|
224
|
+
customer: customer.idn,
|
|
225
|
+
resources: [],
|
|
226
|
+
totalCreated: 0,
|
|
227
|
+
totalUpdated: 0,
|
|
228
|
+
totalDeleted: 0,
|
|
229
|
+
errors: []
|
|
230
|
+
};
|
|
231
|
+
for (const resourceType of resourceTypes) {
|
|
232
|
+
const strategy = this.strategies.get(resourceType);
|
|
233
|
+
if (!strategy) {
|
|
234
|
+
result.errors.push(`Unknown resource type: ${resourceType}`);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const changes = await strategy.getChanges(customer);
|
|
239
|
+
if (changes.length === 0) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
// Validate before push
|
|
243
|
+
const items = changes.map(c => c.item);
|
|
244
|
+
const validation = await strategy.validate(customer, items);
|
|
245
|
+
if (!validation.valid) {
|
|
246
|
+
const errorMessages = validation.errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
247
|
+
throw new ValidationError(`Validation failed: ${errorMessages}`, [validation]);
|
|
248
|
+
}
|
|
249
|
+
const pushResult = await strategy.push(customer, changes);
|
|
250
|
+
result.resources.push({
|
|
251
|
+
resourceType: strategy.resourceType,
|
|
252
|
+
displayName: strategy.displayName,
|
|
253
|
+
result: pushResult
|
|
254
|
+
});
|
|
255
|
+
result.totalCreated += pushResult.created;
|
|
256
|
+
result.totalUpdated += pushResult.updated;
|
|
257
|
+
result.totalDeleted += pushResult.deleted;
|
|
258
|
+
result.errors.push(...pushResult.errors);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
const message = `Failed to push ${strategy.displayName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
262
|
+
this.logger.error(message, error);
|
|
263
|
+
if (this.options.stopOnError) {
|
|
264
|
+
throw new SyncError(message, strategy.resourceType, error instanceof Error ? error : undefined);
|
|
265
|
+
}
|
|
266
|
+
result.errors.push(message);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get status for ALL resources
|
|
273
|
+
*/
|
|
274
|
+
async getStatus(customer) {
|
|
275
|
+
const report = {
|
|
276
|
+
customer: customer.idn,
|
|
277
|
+
resources: [],
|
|
278
|
+
totalChanges: 0
|
|
279
|
+
};
|
|
280
|
+
for (const strategy of this.strategies.values()) {
|
|
281
|
+
try {
|
|
282
|
+
const status = await strategy.getStatus(customer);
|
|
283
|
+
report.resources.push(status);
|
|
284
|
+
report.totalChanges += status.changedCount;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
this.logger.error(`Failed to get status for ${strategy.displayName}`, error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return report;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get status for specific resource types
|
|
294
|
+
*/
|
|
295
|
+
async getStatusSelected(customer, resourceTypes) {
|
|
296
|
+
const report = {
|
|
297
|
+
customer: customer.idn,
|
|
298
|
+
resources: [],
|
|
299
|
+
totalChanges: 0
|
|
300
|
+
};
|
|
301
|
+
for (const resourceType of resourceTypes) {
|
|
302
|
+
const strategy = this.strategies.get(resourceType);
|
|
303
|
+
if (!strategy) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
const status = await strategy.getStatus(customer);
|
|
308
|
+
report.resources.push(status);
|
|
309
|
+
report.totalChanges += status.changedCount;
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
this.logger.error(`Failed to get status for ${strategy.displayName}`, error);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return report;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Helper to execute pull with a single strategy
|
|
319
|
+
*/
|
|
320
|
+
async pullWithStrategy(strategy, customer, options) {
|
|
321
|
+
const result = await strategy.pull(customer, options);
|
|
322
|
+
return {
|
|
323
|
+
resourceType: strategy.resourceType,
|
|
324
|
+
displayName: strategy.displayName,
|
|
325
|
+
result
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Factory function for creating SyncEngine with default strategies
|
|
331
|
+
*/
|
|
332
|
+
export function createSyncEngine(strategies, logger, options) {
|
|
333
|
+
return new SyncEngine(strategies, logger, options);
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=SyncEngine.js.map
|
|
@@ -9,7 +9,7 @@ export async function handleCreateAttributeCommand(customerConfig, args, verbose
|
|
|
9
9
|
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer);
|
|
10
10
|
// Parse arguments
|
|
11
11
|
const idn = args._[1];
|
|
12
|
-
const value = args.value
|
|
12
|
+
const value = args.value !== undefined ? String(args.value) : '';
|
|
13
13
|
const title = args.title || idn;
|
|
14
14
|
const description = args.description || '';
|
|
15
15
|
const group = args.group || 'General';
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Customer Command Handler - Creates a new NEWO customer account
|
|
3
|
+
*
|
|
4
|
+
* This command creates an empty NEWO customer using the v3 API.
|
|
5
|
+
* It requires the api_secret attribute from the source customer account.
|
|
6
|
+
*/
|
|
7
|
+
import { makeClient, createNewoCustomer, getCustomerAttributes } from '../../api.js';
|
|
8
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
9
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
10
|
+
export async function handleCreateCustomerCommand(customerConfig, args, verbose = false) {
|
|
11
|
+
try {
|
|
12
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer);
|
|
13
|
+
// Parse arguments
|
|
14
|
+
const organizationName = args._[1];
|
|
15
|
+
const email = args.email;
|
|
16
|
+
const tenant = args.tenant || 'newo';
|
|
17
|
+
const phone = args.phone || '';
|
|
18
|
+
const comment = args.comment || '';
|
|
19
|
+
const status = args.status || 'temporal';
|
|
20
|
+
const projectIdn = args.project;
|
|
21
|
+
const externalId = args['external-id'];
|
|
22
|
+
// Validate required parameters
|
|
23
|
+
if (!organizationName) {
|
|
24
|
+
console.error('Error: Organization name is required');
|
|
25
|
+
console.error('Usage: newo create-customer <organization_name> --email <email> [options]');
|
|
26
|
+
console.error('');
|
|
27
|
+
console.error('Options:');
|
|
28
|
+
console.error(' --email <email> Owner email (required)');
|
|
29
|
+
console.error(' --tenant <tenant> Tenant name (default: newo)');
|
|
30
|
+
console.error(' --phone <phone> Contact phone number');
|
|
31
|
+
console.error(' --comment <comment> Comment or notes');
|
|
32
|
+
console.error(' --status <status> temporal or permanent (default: temporal)');
|
|
33
|
+
console.error(' --project <idn> Project IDN to install (e.g., naf)');
|
|
34
|
+
console.error(' --external-id <id> External customer ID for tracking');
|
|
35
|
+
console.error('');
|
|
36
|
+
console.error('Example:');
|
|
37
|
+
console.error(' newo create-customer "Acme Corp" --email owner@acme.com --project naf');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (!email) {
|
|
41
|
+
console.error('Error: Owner email is required (--email <email>)');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
if (verbose) {
|
|
45
|
+
console.log(`📝 Creating new NEWO customer...`);
|
|
46
|
+
console.log(` Organization: ${organizationName}`);
|
|
47
|
+
console.log(` Owner Email: ${email}`);
|
|
48
|
+
console.log(` Tenant: ${tenant}`);
|
|
49
|
+
console.log(` Status: ${status}`);
|
|
50
|
+
if (phone)
|
|
51
|
+
console.log(` Phone: ${phone}`);
|
|
52
|
+
if (comment)
|
|
53
|
+
console.log(` Comment: ${comment}`);
|
|
54
|
+
if (projectIdn)
|
|
55
|
+
console.log(` Project: ${projectIdn}`);
|
|
56
|
+
if (externalId)
|
|
57
|
+
console.log(` External ID: ${externalId}`);
|
|
58
|
+
}
|
|
59
|
+
// Get access token and create client
|
|
60
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
61
|
+
const client = await makeClient(verbose, accessToken);
|
|
62
|
+
// Get api_secret from customer attributes
|
|
63
|
+
console.log('🔑 Fetching API secret from customer attributes...');
|
|
64
|
+
const attributesResponse = await getCustomerAttributes(client, true);
|
|
65
|
+
const apiSecretAttr = attributesResponse.attributes.find(attr => attr.idn === 'api_secret');
|
|
66
|
+
if (!apiSecretAttr || !apiSecretAttr.value) {
|
|
67
|
+
console.error('Error: api_secret attribute not found in customer account');
|
|
68
|
+
console.error('This command requires the api_secret attribute to create new customers.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const apiSecret = typeof apiSecretAttr.value === 'string' ? apiSecretAttr.value : String(apiSecretAttr.value);
|
|
72
|
+
if (verbose) {
|
|
73
|
+
console.log(`✅ API secret found (${apiSecret.substring(0, 6)}...)`);
|
|
74
|
+
}
|
|
75
|
+
// Build members array
|
|
76
|
+
const members = [
|
|
77
|
+
{
|
|
78
|
+
email: email,
|
|
79
|
+
role: 'owner',
|
|
80
|
+
tenants: [tenant]
|
|
81
|
+
}
|
|
82
|
+
];
|
|
83
|
+
// Build projects array if project specified
|
|
84
|
+
const projects = [];
|
|
85
|
+
if (projectIdn) {
|
|
86
|
+
projects.push({
|
|
87
|
+
idn: projectIdn
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Build customer object - only include optional fields if they have values
|
|
91
|
+
const customerData = {
|
|
92
|
+
organization_name: organizationName,
|
|
93
|
+
tenant: tenant,
|
|
94
|
+
members: members,
|
|
95
|
+
contact_email: email,
|
|
96
|
+
organization_type: 'customer',
|
|
97
|
+
organization_status: status,
|
|
98
|
+
attributes: [
|
|
99
|
+
{
|
|
100
|
+
idn: 'empty',
|
|
101
|
+
value: 'True'
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
// Add optional fields only if they have values
|
|
106
|
+
if (comment) {
|
|
107
|
+
customerData.comment = comment;
|
|
108
|
+
}
|
|
109
|
+
if (phone) {
|
|
110
|
+
customerData.contact_phone = phone;
|
|
111
|
+
}
|
|
112
|
+
if (externalId) {
|
|
113
|
+
customerData.external_customer_id = externalId;
|
|
114
|
+
}
|
|
115
|
+
// Build customer creation request
|
|
116
|
+
const createRequest = {
|
|
117
|
+
secret: apiSecret,
|
|
118
|
+
customer: customerData
|
|
119
|
+
};
|
|
120
|
+
// Add projects only if specified
|
|
121
|
+
if (projects.length > 0) {
|
|
122
|
+
createRequest.projects = projects;
|
|
123
|
+
}
|
|
124
|
+
if (verbose) {
|
|
125
|
+
console.log('📤 Creating customer with request:');
|
|
126
|
+
console.log(JSON.stringify(createRequest, null, 2));
|
|
127
|
+
}
|
|
128
|
+
// Create the customer
|
|
129
|
+
console.log('🚀 Creating customer...');
|
|
130
|
+
const response = await createNewoCustomer(client, createRequest);
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log('✅ Customer created successfully!');
|
|
133
|
+
console.log(` Customer IDN: ${response.idn}`);
|
|
134
|
+
console.log(` Customer ID: ${response.id}`);
|
|
135
|
+
console.log(` Organization: ${organizationName}`);
|
|
136
|
+
console.log(` Owner: ${email}`);
|
|
137
|
+
if (projectIdn) {
|
|
138
|
+
console.log(` Project: ${projectIdn}`);
|
|
139
|
+
}
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log('📝 Next steps:');
|
|
142
|
+
console.log(` 1. Add the new customer to your .env file:`);
|
|
143
|
+
console.log(` NEWO_${response.idn}_API_KEY=<api_key>`);
|
|
144
|
+
console.log(` 2. Or use the customer_intercom integration to manage the new customer`);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const errMessage = error instanceof Error ? error.message : String(error);
|
|
148
|
+
console.error('❌ Failed to create customer:', errMessage);
|
|
149
|
+
// Provide more detailed error info if available
|
|
150
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
151
|
+
const axiosError = error;
|
|
152
|
+
if (axiosError.response?.data) {
|
|
153
|
+
console.error(' API Error:', JSON.stringify(axiosError.response.data, null, 2));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=create-customer.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Main diff command handler
|
|
4
|
+
*/
|
|
5
|
+
export declare function handleDiffCommand(customerConfig: MultiCustomerConfig, args: CliArgs, verbose: boolean): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=diff.d.ts.map
|