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
|
@@ -1,37 +1,147 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push command handler
|
|
3
|
+
*
|
|
4
|
+
* Supports selective resource sync with --only and --exclude flags:
|
|
5
|
+
* newo push --only projects,attributes
|
|
6
|
+
* newo push --exclude integrations
|
|
7
|
+
* newo push --all (explicit all resources)
|
|
8
|
+
*
|
|
9
|
+
* Available resources: projects, attributes, integrations, akb
|
|
10
|
+
* Note: conversations is read-only and cannot be pushed
|
|
3
11
|
*/
|
|
4
12
|
import { makeClient } from '../../api.js';
|
|
5
13
|
import { pushChanged } from '../../sync.js';
|
|
6
14
|
import { getValidAccessToken } from '../../auth.js';
|
|
7
15
|
import { selectSingleCustomer, interactiveCustomerSelection } from '../customer-selection.js';
|
|
16
|
+
import { setupCli } from '../../cli-new/bootstrap.js';
|
|
17
|
+
import { PUSHABLE_RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
|
|
18
|
+
/**
|
|
19
|
+
* Parse resource list from comma-separated string
|
|
20
|
+
*/
|
|
21
|
+
function parseResourceList(input) {
|
|
22
|
+
if (!input)
|
|
23
|
+
return [];
|
|
24
|
+
return input.split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validate resource types for push
|
|
28
|
+
*/
|
|
29
|
+
function validateResources(resources) {
|
|
30
|
+
const validTypes = new Set(PUSHABLE_RESOURCE_TYPES);
|
|
31
|
+
const valid = [];
|
|
32
|
+
const invalid = [];
|
|
33
|
+
for (const r of resources) {
|
|
34
|
+
if (r === 'conversations') {
|
|
35
|
+
invalid.push(r + ' (read-only)');
|
|
36
|
+
}
|
|
37
|
+
else if (validTypes.has(r)) {
|
|
38
|
+
valid.push(r);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
invalid.push(r);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { valid, invalid };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Push using V2 SyncEngine with selective sync
|
|
48
|
+
*/
|
|
49
|
+
async function pushWithV2Engine(customerConfig, customer, resources, verbose) {
|
|
50
|
+
const { syncEngine, logger } = setupCli(customerConfig, verbose);
|
|
51
|
+
if (resources === 'all') {
|
|
52
|
+
const result = await syncEngine.pushAll(customer);
|
|
53
|
+
logger.info(`✅ Pushed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
|
|
54
|
+
if (result.errors.length > 0) {
|
|
55
|
+
logger.warn(`⚠️ ${result.errors.length} error(s) occurred`);
|
|
56
|
+
result.errors.forEach(e => logger.error(` ${e}`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const result = await syncEngine.pushSelected(customer, resources);
|
|
61
|
+
logger.info(`✅ Pushed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
|
|
62
|
+
if (result.errors.length > 0) {
|
|
63
|
+
logger.warn(`⚠️ ${result.errors.length} error(s) occurred`);
|
|
64
|
+
result.errors.forEach(e => logger.error(` ${e}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
8
68
|
export async function handlePushCommand(customerConfig, args, verbose) {
|
|
9
69
|
const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(customerConfig, args.customer);
|
|
10
70
|
const shouldPublish = !args['no-publish'];
|
|
71
|
+
// Check for selective sync flags
|
|
72
|
+
const onlyResources = parseResourceList(args.only);
|
|
73
|
+
const excludeResources = parseResourceList(args.exclude);
|
|
74
|
+
const pushAllResources = Boolean(args.all);
|
|
75
|
+
// Validate resource types
|
|
76
|
+
if (onlyResources.length > 0) {
|
|
77
|
+
const { invalid } = validateResources(onlyResources);
|
|
78
|
+
if (invalid.length > 0) {
|
|
79
|
+
console.error(`❌ Cannot push resource(s): ${invalid.join(', ')}`);
|
|
80
|
+
console.error(` Available for push: ${PUSHABLE_RESOURCE_TYPES.join(', ')}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (excludeResources.length > 0) {
|
|
85
|
+
const { invalid } = validateResources(excludeResources);
|
|
86
|
+
if (invalid.length > 0 && !invalid.every(r => r.includes('read-only'))) {
|
|
87
|
+
console.error(`❌ Unknown resource type(s): ${invalid.join(', ')}`);
|
|
88
|
+
console.error(` Available: ${PUSHABLE_RESOURCE_TYPES.join(', ')}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Determine which resources to push
|
|
93
|
+
let resourcesToPush = 'all';
|
|
94
|
+
if (onlyResources.length > 0) {
|
|
95
|
+
resourcesToPush = onlyResources;
|
|
96
|
+
console.log(`📦 Pushing selected resources: ${onlyResources.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
else if (excludeResources.length > 0) {
|
|
99
|
+
resourcesToPush = PUSHABLE_RESOURCE_TYPES.filter(r => !excludeResources.includes(r));
|
|
100
|
+
console.log(`📦 Pushing resources (excluding: ${excludeResources.join(', ')})`);
|
|
101
|
+
}
|
|
102
|
+
else if (pushAllResources) {
|
|
103
|
+
resourcesToPush = 'all';
|
|
104
|
+
console.log(`📦 Pushing ALL resources`);
|
|
105
|
+
}
|
|
106
|
+
// Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
|
|
107
|
+
const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pushAllResources;
|
|
11
108
|
if (selectedCustomer) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
109
|
+
if (useV2Engine) {
|
|
110
|
+
await pushWithV2Engine(customerConfig, selectedCustomer, resourcesToPush, verbose);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Legacy behavior
|
|
114
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
115
|
+
const client = await makeClient(verbose, accessToken);
|
|
116
|
+
await pushChanged(client, selectedCustomer, verbose, shouldPublish);
|
|
117
|
+
}
|
|
16
118
|
}
|
|
17
119
|
else if (isMultiCustomer) {
|
|
18
120
|
// Multiple customers exist with no default, ask user
|
|
19
121
|
const customersToProcess = await interactiveCustomerSelection(allCustomers);
|
|
20
122
|
if (customersToProcess.length === 1) {
|
|
21
|
-
// Single customer selected
|
|
22
123
|
const customer = customersToProcess[0];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
124
|
+
if (useV2Engine) {
|
|
125
|
+
await pushWithV2Engine(customerConfig, customer, resourcesToPush, verbose);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const accessToken = await getValidAccessToken(customer);
|
|
129
|
+
const client = await makeClient(verbose, accessToken);
|
|
130
|
+
await pushChanged(client, customer, verbose, shouldPublish);
|
|
131
|
+
}
|
|
26
132
|
}
|
|
27
133
|
else {
|
|
28
|
-
// Multi-customer push (user selected "All customers")
|
|
29
134
|
console.log(`🔄 Pushing to ${customersToProcess.length} customers...`);
|
|
30
135
|
for (const customer of customersToProcess) {
|
|
31
136
|
console.log(`\n📤 Pushing for customer: ${customer.idn}`);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
137
|
+
if (useV2Engine) {
|
|
138
|
+
await pushWithV2Engine(customerConfig, customer, resourcesToPush, verbose);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const accessToken = await getValidAccessToken(customer);
|
|
142
|
+
const client = await makeClient(verbose, accessToken);
|
|
143
|
+
await pushChanged(client, customer, verbose, shouldPublish);
|
|
144
|
+
}
|
|
35
145
|
}
|
|
36
146
|
console.log(`\n✅ Push completed for all ${customersToProcess.length} customers`);
|
|
37
147
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Main watch command handler
|
|
4
|
+
*/
|
|
5
|
+
export declare function handleWatchCommand(customerConfig: MultiCustomerConfig, args: CliArgs, verbose: boolean): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch command handler
|
|
3
|
+
*
|
|
4
|
+
* Watches for file changes and automatically pushes when changes are detected.
|
|
5
|
+
* Supports selective resource watching with --only and --exclude flags.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* newo watch # Watch and push all changes
|
|
9
|
+
* newo watch --only projects # Watch only project files
|
|
10
|
+
* newo watch --debounce 2000 # Custom debounce delay (ms)
|
|
11
|
+
*/
|
|
12
|
+
import { selectSingleCustomer } from '../customer-selection.js';
|
|
13
|
+
import { setupCli } from '../../cli-new/bootstrap.js';
|
|
14
|
+
import { PUSHABLE_RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
|
|
15
|
+
import chokidar from 'chokidar';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
// Default debounce delay in milliseconds
|
|
18
|
+
const DEFAULT_DEBOUNCE_MS = 1000;
|
|
19
|
+
/**
|
|
20
|
+
* Parse resource list from comma-separated string
|
|
21
|
+
*/
|
|
22
|
+
function parseResourceList(input) {
|
|
23
|
+
if (!input)
|
|
24
|
+
return [];
|
|
25
|
+
return input.split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get file patterns to watch based on resource types
|
|
29
|
+
*/
|
|
30
|
+
function getWatchPatterns(customerDir, resources) {
|
|
31
|
+
const patterns = [];
|
|
32
|
+
for (const resource of resources) {
|
|
33
|
+
switch (resource) {
|
|
34
|
+
case 'projects':
|
|
35
|
+
// Watch .guidance and .jinja files in projects
|
|
36
|
+
patterns.push(path.join(customerDir, 'projects', '**', '*.guidance'));
|
|
37
|
+
patterns.push(path.join(customerDir, 'projects', '**', '*.jinja'));
|
|
38
|
+
patterns.push(path.join(customerDir, 'projects', '**', 'metadata.yaml'));
|
|
39
|
+
break;
|
|
40
|
+
case 'attributes':
|
|
41
|
+
// Watch attributes.yaml files
|
|
42
|
+
patterns.push(path.join(customerDir, 'attributes.yaml'));
|
|
43
|
+
patterns.push(path.join(customerDir, 'projects', '*', 'attributes.yaml'));
|
|
44
|
+
break;
|
|
45
|
+
case 'integrations':
|
|
46
|
+
// Watch integration files
|
|
47
|
+
patterns.push(path.join(customerDir, 'integrations', '**', '*.yaml'));
|
|
48
|
+
break;
|
|
49
|
+
case 'akb':
|
|
50
|
+
// Watch AKB files
|
|
51
|
+
patterns.push(path.join(customerDir, 'akb', '**', '*.yaml'));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return patterns;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Push with V2 engine for selective resources
|
|
59
|
+
*/
|
|
60
|
+
async function pushWithV2Engine(customerConfig, customer, resources, verbose) {
|
|
61
|
+
const { syncEngine, logger } = setupCli(customerConfig, verbose);
|
|
62
|
+
const result = await syncEngine.pushSelected(customer, resources);
|
|
63
|
+
if (result.totalCreated > 0 || result.totalUpdated > 0 || result.totalDeleted > 0) {
|
|
64
|
+
logger.info(`✅ Pushed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
|
|
65
|
+
}
|
|
66
|
+
if (result.errors.length > 0) {
|
|
67
|
+
logger.warn(`⚠️ ${result.errors.length} error(s) occurred`);
|
|
68
|
+
result.errors.forEach(e => logger.error(` ${e}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Main watch command handler
|
|
73
|
+
*/
|
|
74
|
+
export async function handleWatchCommand(customerConfig, args, verbose) {
|
|
75
|
+
const { selectedCustomer } = selectSingleCustomer(customerConfig, args.customer);
|
|
76
|
+
if (!selectedCustomer) {
|
|
77
|
+
console.error('❌ Please specify a customer with --customer <idn> or set a default');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
// Parse options
|
|
81
|
+
const onlyResources = parseResourceList(args.only);
|
|
82
|
+
const excludeResources = parseResourceList(args.exclude);
|
|
83
|
+
const debounceMs = typeof args.debounce === 'number'
|
|
84
|
+
? args.debounce
|
|
85
|
+
: (typeof args.debounce === 'string' ? parseInt(args.debounce, 10) : DEFAULT_DEBOUNCE_MS);
|
|
86
|
+
// Determine resources to watch
|
|
87
|
+
let resourcesToWatch;
|
|
88
|
+
if (onlyResources.length > 0) {
|
|
89
|
+
resourcesToWatch = onlyResources.filter(r => PUSHABLE_RESOURCE_TYPES.includes(r));
|
|
90
|
+
}
|
|
91
|
+
else if (excludeResources.length > 0) {
|
|
92
|
+
resourcesToWatch = PUSHABLE_RESOURCE_TYPES.filter(r => !excludeResources.includes(r));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
resourcesToWatch = [...PUSHABLE_RESOURCE_TYPES];
|
|
96
|
+
}
|
|
97
|
+
if (resourcesToWatch.length === 0) {
|
|
98
|
+
console.error('❌ No valid resources to watch');
|
|
99
|
+
console.error(` Available: ${PUSHABLE_RESOURCE_TYPES.join(', ')}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const customerDir = path.join(process.cwd(), 'newo_customers', selectedCustomer.idn);
|
|
103
|
+
const watchPatterns = getWatchPatterns(customerDir, resourcesToWatch);
|
|
104
|
+
console.log(`👀 Watching for changes in: ${resourcesToWatch.join(', ')}`);
|
|
105
|
+
console.log(`📁 Customer: ${selectedCustomer.idn}`);
|
|
106
|
+
console.log(`⏱️ Debounce: ${debounceMs}ms`);
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log('Press Ctrl+C to stop watching.');
|
|
109
|
+
console.log('');
|
|
110
|
+
// Debounce state
|
|
111
|
+
let debounceTimer = null;
|
|
112
|
+
let pendingChanges = new Set();
|
|
113
|
+
let isPushing = false;
|
|
114
|
+
// Push function with debouncing
|
|
115
|
+
const debouncedPush = () => {
|
|
116
|
+
if (debounceTimer) {
|
|
117
|
+
clearTimeout(debounceTimer);
|
|
118
|
+
}
|
|
119
|
+
debounceTimer = setTimeout(async () => {
|
|
120
|
+
if (isPushing) {
|
|
121
|
+
// If already pushing, wait and try again
|
|
122
|
+
debouncedPush();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (pendingChanges.size === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const changedFiles = Array.from(pendingChanges);
|
|
129
|
+
pendingChanges.clear();
|
|
130
|
+
isPushing = true;
|
|
131
|
+
console.log(`\n🔄 Changes detected in ${changedFiles.length} file(s):`);
|
|
132
|
+
changedFiles.slice(0, 5).forEach(f => console.log(` ${path.relative(process.cwd(), f)}`));
|
|
133
|
+
if (changedFiles.length > 5) {
|
|
134
|
+
console.log(` ... and ${changedFiles.length - 5} more`);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
// Use V2 engine for selective push
|
|
138
|
+
await pushWithV2Engine(customerConfig, selectedCustomer, resourcesToWatch, verbose);
|
|
139
|
+
console.log('✅ Push completed');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error('❌ Push failed:', error instanceof Error ? error.message : String(error));
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
isPushing = false;
|
|
146
|
+
}
|
|
147
|
+
}, debounceMs);
|
|
148
|
+
};
|
|
149
|
+
// Set up file watcher
|
|
150
|
+
const watcher = chokidar.watch(watchPatterns, {
|
|
151
|
+
persistent: true,
|
|
152
|
+
ignoreInitial: true,
|
|
153
|
+
awaitWriteFinish: {
|
|
154
|
+
stabilityThreshold: 300,
|
|
155
|
+
pollInterval: 100
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
watcher
|
|
159
|
+
.on('change', (filePath) => {
|
|
160
|
+
pendingChanges.add(filePath);
|
|
161
|
+
if (verbose) {
|
|
162
|
+
console.log(`📝 Changed: ${path.relative(process.cwd(), filePath)}`);
|
|
163
|
+
}
|
|
164
|
+
debouncedPush();
|
|
165
|
+
})
|
|
166
|
+
.on('add', (filePath) => {
|
|
167
|
+
pendingChanges.add(filePath);
|
|
168
|
+
if (verbose) {
|
|
169
|
+
console.log(`➕ Added: ${path.relative(process.cwd(), filePath)}`);
|
|
170
|
+
}
|
|
171
|
+
debouncedPush();
|
|
172
|
+
})
|
|
173
|
+
.on('unlink', (filePath) => {
|
|
174
|
+
pendingChanges.add(filePath);
|
|
175
|
+
if (verbose) {
|
|
176
|
+
console.log(`➖ Removed: ${path.relative(process.cwd(), filePath)}`);
|
|
177
|
+
}
|
|
178
|
+
debouncedPush();
|
|
179
|
+
})
|
|
180
|
+
.on('error', (error) => {
|
|
181
|
+
console.error('❌ Watcher error:', error);
|
|
182
|
+
});
|
|
183
|
+
// Handle graceful shutdown
|
|
184
|
+
process.on('SIGINT', () => {
|
|
185
|
+
console.log('\n\n👋 Stopping watch...');
|
|
186
|
+
watcher.close();
|
|
187
|
+
if (debounceTimer) {
|
|
188
|
+
clearTimeout(debounceTimer);
|
|
189
|
+
}
|
|
190
|
+
process.exit(0);
|
|
191
|
+
});
|
|
192
|
+
// Keep the process running
|
|
193
|
+
await new Promise(() => { }); // Never resolves - keeps process alive
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap - Application Initialization and DI Setup
|
|
3
|
+
*
|
|
4
|
+
* This file wires all dependencies together and creates the service container.
|
|
5
|
+
* It's the single entry point for configuring the application.
|
|
6
|
+
*/
|
|
7
|
+
import { ServiceContainer } from './di/Container.js';
|
|
8
|
+
import { type ILogger, type CustomerConfig, type MultiCustomerConfig } from '../domain/resources/common/types.js';
|
|
9
|
+
import { SyncEngine, type SyncEngineOptions } from '../application/sync/SyncEngine.js';
|
|
10
|
+
import { MigrationEngine } from '../application/migration/MigrationEngine.js';
|
|
11
|
+
import type { AxiosInstance } from 'axios';
|
|
12
|
+
/**
|
|
13
|
+
* API Client Factory that creates authenticated clients
|
|
14
|
+
*/
|
|
15
|
+
export declare function createApiClient(customer: CustomerConfig, verbose: boolean): Promise<AxiosInstance>;
|
|
16
|
+
/**
|
|
17
|
+
* Bootstrap options
|
|
18
|
+
*/
|
|
19
|
+
export interface BootstrapOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Enable verbose logging
|
|
22
|
+
*/
|
|
23
|
+
verbose?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Sync engine options
|
|
26
|
+
*/
|
|
27
|
+
syncEngineOptions?: SyncEngineOptions;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create and configure the service container
|
|
31
|
+
*/
|
|
32
|
+
export declare function createServiceContainer(customerConfig: MultiCustomerConfig, options?: BootstrapOptions): ServiceContainer;
|
|
33
|
+
/**
|
|
34
|
+
* Get SyncEngine from container
|
|
35
|
+
*/
|
|
36
|
+
export declare function getSyncEngine(container: ServiceContainer): SyncEngine;
|
|
37
|
+
/**
|
|
38
|
+
* Get MigrationEngine from container
|
|
39
|
+
*/
|
|
40
|
+
export declare function getMigrationEngine(container: ServiceContainer): MigrationEngine;
|
|
41
|
+
/**
|
|
42
|
+
* Get Logger from container
|
|
43
|
+
*/
|
|
44
|
+
export declare function getLogger(container: ServiceContainer): ILogger;
|
|
45
|
+
/**
|
|
46
|
+
* Quick setup for CLI commands
|
|
47
|
+
*
|
|
48
|
+
* Creates a configured container with all services ready to use.
|
|
49
|
+
*/
|
|
50
|
+
export declare function setupCli(customerConfig: MultiCustomerConfig, verbose?: boolean): {
|
|
51
|
+
container: ServiceContainer;
|
|
52
|
+
syncEngine: SyncEngine;
|
|
53
|
+
migrationEngine: MigrationEngine;
|
|
54
|
+
logger: ILogger;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Adapter function for legacy pull command
|
|
58
|
+
*
|
|
59
|
+
* This provides backward compatibility with the existing CLI.
|
|
60
|
+
*/
|
|
61
|
+
export declare function legacyPullAdapter(customerConfig: MultiCustomerConfig, customer: CustomerConfig, verbose: boolean, silentOverwrite: boolean): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Adapter function for legacy push command
|
|
64
|
+
*/
|
|
65
|
+
export declare function legacyPushAdapter(customerConfig: MultiCustomerConfig, customer: CustomerConfig, verbose: boolean): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Adapter function for legacy status command
|
|
68
|
+
*/
|
|
69
|
+
export declare function legacyStatusAdapter(customerConfig: MultiCustomerConfig, customer: CustomerConfig, verbose: boolean): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Adapter function for legacy migrate command
|
|
72
|
+
*/
|
|
73
|
+
export declare function legacyMigrateAdapter(customerConfig: MultiCustomerConfig, sourceCustomer: CustomerConfig, destCustomer: CustomerConfig, sourceClient: AxiosInstance, destClient: AxiosInstance, verbose: boolean): Promise<void>;
|
|
74
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap - Application Initialization and DI Setup
|
|
3
|
+
*
|
|
4
|
+
* This file wires all dependencies together and creates the service container.
|
|
5
|
+
* It's the single entry point for configuring the application.
|
|
6
|
+
*/
|
|
7
|
+
import { ServiceContainer } from './di/Container.js';
|
|
8
|
+
import { TOKENS } from './di/tokens.js';
|
|
9
|
+
import { ConsoleLogger } from '../domain/resources/common/types.js';
|
|
10
|
+
import { SyncEngine } from '../application/sync/SyncEngine.js';
|
|
11
|
+
import { MigrationEngine, TransformService } from '../application/migration/MigrationEngine.js';
|
|
12
|
+
import { ProjectSyncStrategy, createProjectSyncStrategy } from '../domain/strategies/sync/ProjectSyncStrategy.js';
|
|
13
|
+
import { AttributeSyncStrategy, createAttributeSyncStrategy } from '../domain/strategies/sync/AttributeSyncStrategy.js';
|
|
14
|
+
import { IntegrationSyncStrategy, createIntegrationSyncStrategy } from '../domain/strategies/sync/IntegrationSyncStrategy.js';
|
|
15
|
+
import { AkbSyncStrategy, createAkbSyncStrategy } from '../domain/strategies/sync/AkbSyncStrategy.js';
|
|
16
|
+
import { ConversationSyncStrategy, createConversationSyncStrategy } from '../domain/strategies/sync/ConversationSyncStrategy.js';
|
|
17
|
+
import { makeClient } from '../api.js';
|
|
18
|
+
import { getValidAccessToken } from '../auth.js';
|
|
19
|
+
/**
|
|
20
|
+
* API Client Factory that creates authenticated clients
|
|
21
|
+
*/
|
|
22
|
+
export async function createApiClient(customer, verbose) {
|
|
23
|
+
// Set environment variables for the customer
|
|
24
|
+
process.env.NEWO_API_KEY = customer.apiKey;
|
|
25
|
+
if (customer.projectId) {
|
|
26
|
+
process.env.NEWO_PROJECT_ID = customer.projectId;
|
|
27
|
+
}
|
|
28
|
+
const token = await getValidAccessToken();
|
|
29
|
+
return makeClient(verbose, token);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create and configure the service container
|
|
33
|
+
*/
|
|
34
|
+
export function createServiceContainer(customerConfig, options = {}) {
|
|
35
|
+
const container = new ServiceContainer();
|
|
36
|
+
const verbose = options.verbose ?? false;
|
|
37
|
+
// === Infrastructure Layer ===
|
|
38
|
+
// Logger
|
|
39
|
+
const logger = new ConsoleLogger(verbose);
|
|
40
|
+
container.registerValue(TOKENS.LOGGER, logger);
|
|
41
|
+
// Customer Config
|
|
42
|
+
container.registerValue(TOKENS.CUSTOMER_CONFIG, customerConfig);
|
|
43
|
+
// API Client Factory
|
|
44
|
+
container.registerValue(TOKENS.API_CLIENT_FACTORY, createApiClient);
|
|
45
|
+
// === Domain Layer - Sync Strategies ===
|
|
46
|
+
// Project Sync Strategy
|
|
47
|
+
container.registerSingleton(TOKENS.PROJECT_SYNC_STRATEGY, () => createProjectSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
|
|
48
|
+
// Attribute Sync Strategy
|
|
49
|
+
container.registerSingleton(TOKENS.ATTRIBUTE_SYNC_STRATEGY, () => createAttributeSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
|
|
50
|
+
// Integration Sync Strategy
|
|
51
|
+
container.registerSingleton(TOKENS.INTEGRATION_SYNC_STRATEGY, () => createIntegrationSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
|
|
52
|
+
// AKB Sync Strategy
|
|
53
|
+
container.registerSingleton(TOKENS.AKB_SYNC_STRATEGY, () => createAkbSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
|
|
54
|
+
// Conversation Sync Strategy
|
|
55
|
+
container.registerSingleton(TOKENS.CONVERSATION_SYNC_STRATEGY, () => createConversationSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
|
|
56
|
+
// === Application Layer ===
|
|
57
|
+
// Sync Engine (uses all sync strategies)
|
|
58
|
+
container.registerSingleton(TOKENS.SYNC_ENGINE, () => {
|
|
59
|
+
const strategies = [
|
|
60
|
+
container.get(TOKENS.PROJECT_SYNC_STRATEGY),
|
|
61
|
+
container.get(TOKENS.ATTRIBUTE_SYNC_STRATEGY),
|
|
62
|
+
container.get(TOKENS.INTEGRATION_SYNC_STRATEGY),
|
|
63
|
+
container.get(TOKENS.AKB_SYNC_STRATEGY),
|
|
64
|
+
container.get(TOKENS.CONVERSATION_SYNC_STRATEGY),
|
|
65
|
+
];
|
|
66
|
+
return new SyncEngine(strategies, container.get(TOKENS.LOGGER), options.syncEngineOptions);
|
|
67
|
+
});
|
|
68
|
+
// Migration Engine (uses SyncEngine)
|
|
69
|
+
container.registerSingleton(TOKENS.MIGRATION_ENGINE, () => {
|
|
70
|
+
const syncEngine = container.get(TOKENS.SYNC_ENGINE);
|
|
71
|
+
const transformService = new TransformService(container.get(TOKENS.LOGGER));
|
|
72
|
+
return new MigrationEngine(syncEngine, transformService, container.get(TOKENS.LOGGER));
|
|
73
|
+
});
|
|
74
|
+
return container;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get SyncEngine from container
|
|
78
|
+
*/
|
|
79
|
+
export function getSyncEngine(container) {
|
|
80
|
+
return container.get(TOKENS.SYNC_ENGINE);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get MigrationEngine from container
|
|
84
|
+
*/
|
|
85
|
+
export function getMigrationEngine(container) {
|
|
86
|
+
return container.get(TOKENS.MIGRATION_ENGINE);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get Logger from container
|
|
90
|
+
*/
|
|
91
|
+
export function getLogger(container) {
|
|
92
|
+
return container.get(TOKENS.LOGGER);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Quick setup for CLI commands
|
|
96
|
+
*
|
|
97
|
+
* Creates a configured container with all services ready to use.
|
|
98
|
+
*/
|
|
99
|
+
export function setupCli(customerConfig, verbose = false) {
|
|
100
|
+
const container = createServiceContainer(customerConfig, { verbose });
|
|
101
|
+
return {
|
|
102
|
+
container,
|
|
103
|
+
syncEngine: getSyncEngine(container),
|
|
104
|
+
migrationEngine: getMigrationEngine(container),
|
|
105
|
+
logger: getLogger(container)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Adapter function for legacy pull command
|
|
110
|
+
*
|
|
111
|
+
* This provides backward compatibility with the existing CLI.
|
|
112
|
+
*/
|
|
113
|
+
export async function legacyPullAdapter(customerConfig, customer, verbose, silentOverwrite) {
|
|
114
|
+
const { syncEngine } = setupCli(customerConfig, verbose);
|
|
115
|
+
await syncEngine.pullAll(customer, {
|
|
116
|
+
silentOverwrite,
|
|
117
|
+
verbose
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Adapter function for legacy push command
|
|
122
|
+
*/
|
|
123
|
+
export async function legacyPushAdapter(customerConfig, customer, verbose) {
|
|
124
|
+
const { syncEngine } = setupCli(customerConfig, verbose);
|
|
125
|
+
await syncEngine.pushAll(customer);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Adapter function for legacy status command
|
|
129
|
+
*/
|
|
130
|
+
export async function legacyStatusAdapter(customerConfig, customer, verbose) {
|
|
131
|
+
const { syncEngine, logger } = setupCli(customerConfig, verbose);
|
|
132
|
+
const status = await syncEngine.getStatus(customer);
|
|
133
|
+
logger.info(`\nStatus for customer: ${status.customer}`);
|
|
134
|
+
logger.info(`Total changes: ${status.totalChanges}\n`);
|
|
135
|
+
for (const resource of status.resources) {
|
|
136
|
+
if (resource.changedCount > 0) {
|
|
137
|
+
logger.info(`${resource.displayName}: ${resource.changedCount} change(s)`);
|
|
138
|
+
for (const change of resource.changes) {
|
|
139
|
+
logger.info(` ${change.operation.toUpperCase()[0]} ${change.path}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (status.totalChanges === 0) {
|
|
144
|
+
logger.info('No changes to push.');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Adapter function for legacy migrate command
|
|
149
|
+
*/
|
|
150
|
+
export async function legacyMigrateAdapter(customerConfig, sourceCustomer, destCustomer, sourceClient, destClient, verbose) {
|
|
151
|
+
const { migrationEngine } = setupCli(customerConfig, verbose);
|
|
152
|
+
await migrationEngine.migrateAccount(sourceCustomer, destCustomer, sourceClient, destClient, { verbose });
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Injection Container
|
|
3
|
+
*
|
|
4
|
+
* A simple, TypeScript-native DI container that:
|
|
5
|
+
* - Supports singleton and factory registrations
|
|
6
|
+
* - Provides type-safe dependency resolution
|
|
7
|
+
* - Enables easy testing through dependency injection
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Factory function type for creating service instances
|
|
11
|
+
*/
|
|
12
|
+
type Factory<T> = (container: ServiceContainer) => T;
|
|
13
|
+
/**
|
|
14
|
+
* Service Container for Dependency Injection
|
|
15
|
+
*/
|
|
16
|
+
export declare class ServiceContainer {
|
|
17
|
+
private registrations;
|
|
18
|
+
/**
|
|
19
|
+
* Register a factory that creates a new instance each time
|
|
20
|
+
*/
|
|
21
|
+
register<T>(token: symbol, factory: Factory<T>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Register a singleton - only created once
|
|
24
|
+
*/
|
|
25
|
+
registerSingleton<T>(token: symbol, instanceOrFactory: T | Factory<T>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Register a value directly
|
|
28
|
+
*/
|
|
29
|
+
registerValue<T>(token: symbol, value: T): void;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a service by its token
|
|
32
|
+
*/
|
|
33
|
+
get<T>(token: symbol): T;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a token is registered
|
|
36
|
+
*/
|
|
37
|
+
has(token: symbol): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Get all registered tokens
|
|
40
|
+
*/
|
|
41
|
+
getTokens(): symbol[];
|
|
42
|
+
/**
|
|
43
|
+
* Clear all registrations (useful for testing)
|
|
44
|
+
*/
|
|
45
|
+
clear(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Create a child container that inherits parent registrations
|
|
48
|
+
*/
|
|
49
|
+
createChild(): ServiceContainer;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the global container instance
|
|
53
|
+
*/
|
|
54
|
+
export declare function getContainer(): ServiceContainer;
|
|
55
|
+
/**
|
|
56
|
+
* Set the global container instance (useful for testing)
|
|
57
|
+
*/
|
|
58
|
+
export declare function setContainer(container: ServiceContainer): void;
|
|
59
|
+
/**
|
|
60
|
+
* Reset the global container (useful for testing)
|
|
61
|
+
*/
|
|
62
|
+
export declare function resetContainer(): void;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=Container.d.ts.map
|