newo 3.3.3 → 3.4.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/CHANGELOG.md +41 -0
- package/dist/api.d.ts +6 -1
- package/dist/api.js +63 -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/add-project.d.ts +3 -0
- package/dist/cli/commands/add-project.js +136 -0
- 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 +75 -4
- package/dist/cli/commands/list-registries.d.ts +3 -0
- package/dist/cli/commands/list-registries.js +39 -0
- package/dist/cli/commands/list-registry-items.d.ts +3 -0
- package/dist/cli/commands/list-registry-items.js +112 -0
- 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/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 +28 -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 +162 -0
- package/package.json +3 -1
- package/src/api.ts +77 -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/add-project.ts +159 -0
- package/src/cli/commands/create-customer.ts +185 -0
- package/src/cli/commands/diff.ts +360 -0
- package/src/cli/commands/help.ts +75 -4
- package/src/cli/commands/list-registries.ts +53 -0
- package/src/cli/commands/list-registry-items.ts +149 -0
- 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/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 +35 -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 +178 -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
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add Project from Registry Command Handler - Installs a project template from registry
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient, listRegistries, listRegistryItems, addProjectFromRegistry } from '../../api.js';
|
|
5
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
6
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
7
|
+
export async function handleAddProjectCommand(customerConfig, args, verbose = false) {
|
|
8
|
+
try {
|
|
9
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer);
|
|
10
|
+
// Parse arguments
|
|
11
|
+
const projectIdn = args._[1];
|
|
12
|
+
const registryIdn = args.registry || 'production';
|
|
13
|
+
const registryItemIdn = args.item;
|
|
14
|
+
const registryItemVersion = args.version || null;
|
|
15
|
+
const title = args.title || projectIdn || registryItemIdn;
|
|
16
|
+
const description = args.description || '';
|
|
17
|
+
const isAutoUpdateEnabled = Boolean(args['auto-update']);
|
|
18
|
+
// Validate required arguments
|
|
19
|
+
if (!registryItemIdn) {
|
|
20
|
+
console.error('Error: Registry item IDN is required');
|
|
21
|
+
console.error('');
|
|
22
|
+
console.error('Usage: newo add-project <project-idn> --item <registry-item-idn> [options]');
|
|
23
|
+
console.error('');
|
|
24
|
+
console.error('Options:');
|
|
25
|
+
console.error(' --item <idn> Registry item/template IDN (required)');
|
|
26
|
+
console.error(' --registry <idn> Registry to use (default: production)');
|
|
27
|
+
console.error(' --version <version> Specific version to install (default: latest)');
|
|
28
|
+
console.error(' --title <title> Project title (default: project IDN)');
|
|
29
|
+
console.error(' --description <desc> Project description');
|
|
30
|
+
console.error(' --auto-update Enable automatic updates from registry');
|
|
31
|
+
console.error('');
|
|
32
|
+
console.error('Examples:');
|
|
33
|
+
console.error(' newo add-project my_weather --item weather_integration');
|
|
34
|
+
console.error(' newo add-project my_calcom --item cal_com_integration --registry production');
|
|
35
|
+
console.error(' newo add-project my_zoho --item zoho_integration --version 1.0.2 --auto-update');
|
|
36
|
+
console.error('');
|
|
37
|
+
console.error('Run "newo list-registries" to see available registries');
|
|
38
|
+
console.error('Run "newo list-registry-items <registry-idn>" to see available project templates');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
// Use registry item IDN as project IDN if not specified
|
|
42
|
+
const finalProjectIdn = projectIdn || registryItemIdn;
|
|
43
|
+
if (verbose) {
|
|
44
|
+
console.log(`📦 Adding project from registry`);
|
|
45
|
+
console.log(` Project IDN: ${finalProjectIdn}`);
|
|
46
|
+
console.log(` Title: ${title}`);
|
|
47
|
+
console.log(` Registry: ${registryIdn}`);
|
|
48
|
+
console.log(` Item: ${registryItemIdn}`);
|
|
49
|
+
console.log(` Version: ${registryItemVersion || 'latest'}`);
|
|
50
|
+
console.log(` Auto-update: ${isAutoUpdateEnabled}`);
|
|
51
|
+
console.log(` Customer: ${selectedCustomer.idn}`);
|
|
52
|
+
}
|
|
53
|
+
// Get access token and create client
|
|
54
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
55
|
+
const client = await makeClient(verbose, accessToken);
|
|
56
|
+
// Validate registry exists
|
|
57
|
+
console.log(`🔍 Validating registry "${registryIdn}"...`);
|
|
58
|
+
const registries = await listRegistries(client);
|
|
59
|
+
const registry = registries.find((r) => r.idn === registryIdn);
|
|
60
|
+
if (!registry) {
|
|
61
|
+
console.error(`❌ Registry "${registryIdn}" not found`);
|
|
62
|
+
console.error('');
|
|
63
|
+
console.error('Available registries:');
|
|
64
|
+
for (const r of registries) {
|
|
65
|
+
console.error(` • ${r.idn}`);
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
// Validate registry item exists and find version
|
|
70
|
+
console.log(`🔍 Validating project template "${registryItemIdn}"...`);
|
|
71
|
+
const items = await listRegistryItems(client, registry.id);
|
|
72
|
+
const matchingItems = items.filter((item) => item.idn === registryItemIdn);
|
|
73
|
+
if (matchingItems.length === 0) {
|
|
74
|
+
console.error(`❌ Project template "${registryItemIdn}" not found in "${registryIdn}" registry`);
|
|
75
|
+
console.error('');
|
|
76
|
+
console.error('Run "newo list-registry-items ' + registryIdn + '" to see available templates');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
// Find the specific version or latest
|
|
80
|
+
let selectedItem;
|
|
81
|
+
if (registryItemVersion) {
|
|
82
|
+
selectedItem = matchingItems.find((item) => item.version === registryItemVersion);
|
|
83
|
+
if (!selectedItem) {
|
|
84
|
+
console.error(`❌ Version "${registryItemVersion}" not found for "${registryItemIdn}"`);
|
|
85
|
+
console.error('');
|
|
86
|
+
console.error('Available versions:');
|
|
87
|
+
const sortedItems = [...matchingItems].sort((a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime());
|
|
88
|
+
for (const item of sortedItems.slice(0, 10)) {
|
|
89
|
+
console.error(` • ${item.version} (published: ${new Date(item.published_at).toISOString().split('T')[0]})`);
|
|
90
|
+
}
|
|
91
|
+
if (sortedItems.length > 10) {
|
|
92
|
+
console.error(` ... and ${sortedItems.length - 10} more`);
|
|
93
|
+
}
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Get latest version (sorted by published_at desc)
|
|
99
|
+
const sortedItems = [...matchingItems].sort((a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime());
|
|
100
|
+
selectedItem = sortedItems[0];
|
|
101
|
+
}
|
|
102
|
+
if (!selectedItem) {
|
|
103
|
+
console.error(`❌ Could not determine version for "${registryItemIdn}"`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
console.log(`📥 Installing "${registryItemIdn}" v${selectedItem.version} as "${finalProjectIdn}"...`);
|
|
107
|
+
// Create project from registry
|
|
108
|
+
const projectData = {
|
|
109
|
+
idn: finalProjectIdn,
|
|
110
|
+
title,
|
|
111
|
+
version: '',
|
|
112
|
+
description,
|
|
113
|
+
is_auto_update_enabled: isAutoUpdateEnabled,
|
|
114
|
+
registry_idn: registryIdn,
|
|
115
|
+
registry_item_idn: registryItemIdn,
|
|
116
|
+
registry_item_version: registryItemVersion
|
|
117
|
+
};
|
|
118
|
+
const response = await addProjectFromRegistry(client, projectData);
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(`✅ Project installed successfully!`);
|
|
121
|
+
console.log(` Project IDN: ${finalProjectIdn}`);
|
|
122
|
+
console.log(` Project ID: ${response.id}`);
|
|
123
|
+
console.log(` Source: ${registryItemIdn} v${selectedItem.version}`);
|
|
124
|
+
console.log(` Registry: ${registryIdn}`);
|
|
125
|
+
if (isAutoUpdateEnabled) {
|
|
126
|
+
console.log(` Auto-update: Enabled`);
|
|
127
|
+
}
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(`💡 Run "newo pull" to sync the project locally`);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error('❌ Failed to add project from registry:', error instanceof Error ? error.message : String(error));
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=add-project.js.map
|