@memberjunction/metadata-sync 2.54.0 → 2.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -51
- package/dist/index.d.ts +21 -1
- package/dist/index.js +41 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/file-backup-manager.js +2 -2
- package/dist/lib/file-backup-manager.js.map +1 -1
- package/dist/lib/sql-logger.d.ts +44 -0
- package/dist/lib/sql-logger.js +140 -0
- package/dist/lib/sql-logger.js.map +1 -0
- package/dist/lib/sync-engine.js +2 -2
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/lib/transaction-manager.d.ts +36 -0
- package/dist/lib/transaction-manager.js +117 -0
- package/dist/lib/transaction-manager.js.map +1 -0
- package/dist/services/FileResetService.d.ts +30 -0
- package/dist/services/FileResetService.js +182 -0
- package/dist/services/FileResetService.js.map +1 -0
- package/dist/services/InitService.d.ts +17 -0
- package/dist/services/InitService.js +118 -0
- package/dist/services/InitService.js.map +1 -0
- package/dist/services/PullService.d.ts +45 -0
- package/dist/services/PullService.js +564 -0
- package/dist/services/PullService.js.map +1 -0
- package/dist/services/PushService.d.ts +45 -0
- package/dist/services/PushService.js +394 -0
- package/dist/services/PushService.js.map +1 -0
- package/dist/services/StatusService.d.ts +32 -0
- package/dist/services/StatusService.js +138 -0
- package/dist/services/StatusService.js.map +1 -0
- package/dist/services/WatchService.d.ts +32 -0
- package/dist/services/WatchService.js +242 -0
- package/dist/services/WatchService.js.map +1 -0
- package/dist/services/index.d.ts +16 -0
- package/dist/services/index.js +28 -0
- package/dist/services/index.js.map +1 -0
- package/package.json +14 -45
- package/bin/debug.js +0 -7
- package/bin/run +0 -17
- package/bin/run.js +0 -6
- package/dist/commands/file-reset/index.d.ts +0 -15
- package/dist/commands/file-reset/index.js +0 -221
- package/dist/commands/file-reset/index.js.map +0 -1
- package/dist/commands/init/index.d.ts +0 -7
- package/dist/commands/init/index.js +0 -155
- package/dist/commands/init/index.js.map +0 -1
- package/dist/commands/pull/index.d.ts +0 -246
- package/dist/commands/pull/index.js +0 -1448
- package/dist/commands/pull/index.js.map +0 -1
- package/dist/commands/push/index.d.ts +0 -41
- package/dist/commands/push/index.js +0 -1131
- package/dist/commands/push/index.js.map +0 -1
- package/dist/commands/status/index.d.ts +0 -10
- package/dist/commands/status/index.js +0 -199
- package/dist/commands/status/index.js.map +0 -1
- package/dist/commands/validate/index.d.ts +0 -15
- package/dist/commands/validate/index.js +0 -149
- package/dist/commands/validate/index.js.map +0 -1
- package/dist/commands/watch/index.d.ts +0 -15
- package/dist/commands/watch/index.js +0 -300
- package/dist/commands/watch/index.js.map +0 -1
- package/dist/hooks/init.d.ts +0 -3
- package/dist/hooks/init.js +0 -59
- package/dist/hooks/init.js.map +0 -1
- package/oclif.manifest.json +0 -376
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SyncEngine } from '../lib/sync-engine';
|
|
2
|
+
export interface StatusOptions {
|
|
3
|
+
dir?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface StatusCallbacks {
|
|
6
|
+
onProgress?: (message: string) => void;
|
|
7
|
+
onLog?: (message: string) => void;
|
|
8
|
+
onWarn?: (message: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface StatusResult {
|
|
11
|
+
new: number;
|
|
12
|
+
modified: number;
|
|
13
|
+
deleted: number;
|
|
14
|
+
unchanged: number;
|
|
15
|
+
}
|
|
16
|
+
export interface EntityStatusResult {
|
|
17
|
+
entityName: string;
|
|
18
|
+
directory: string;
|
|
19
|
+
new: number;
|
|
20
|
+
modified: number;
|
|
21
|
+
deleted: number;
|
|
22
|
+
unchanged: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class StatusService {
|
|
25
|
+
private syncEngine;
|
|
26
|
+
constructor(syncEngine: SyncEngine);
|
|
27
|
+
checkStatus(options: StatusOptions, callbacks?: StatusCallbacks): Promise<{
|
|
28
|
+
summary: StatusResult;
|
|
29
|
+
details: EntityStatusResult[];
|
|
30
|
+
}>;
|
|
31
|
+
private checkEntityDirectory;
|
|
32
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StatusService = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const provider_utils_1 = require("../lib/provider-utils");
|
|
12
|
+
class StatusService {
|
|
13
|
+
syncEngine;
|
|
14
|
+
constructor(syncEngine) {
|
|
15
|
+
this.syncEngine = syncEngine;
|
|
16
|
+
}
|
|
17
|
+
async checkStatus(options, callbacks) {
|
|
18
|
+
const entityDirs = (0, provider_utils_1.findEntityDirectories)(process.cwd(), options.dir);
|
|
19
|
+
if (entityDirs.length === 0) {
|
|
20
|
+
throw new Error('No entity directories found');
|
|
21
|
+
}
|
|
22
|
+
callbacks?.onLog?.(`Found ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} to check`);
|
|
23
|
+
const details = [];
|
|
24
|
+
let totalNew = 0;
|
|
25
|
+
let totalModified = 0;
|
|
26
|
+
let totalDeleted = 0;
|
|
27
|
+
let totalUnchanged = 0;
|
|
28
|
+
for (const entityDir of entityDirs) {
|
|
29
|
+
const entityConfig = await (0, config_1.loadEntityConfig)(entityDir);
|
|
30
|
+
if (!entityConfig) {
|
|
31
|
+
callbacks?.onWarn?.(`Skipping ${entityDir} - no valid entity configuration`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
callbacks?.onLog?.(`Checking ${entityConfig.entity} in ${entityDir}`);
|
|
35
|
+
const result = await this.checkEntityDirectory(entityDir, entityConfig, callbacks);
|
|
36
|
+
details.push({
|
|
37
|
+
entityName: entityConfig.entity,
|
|
38
|
+
directory: entityDir,
|
|
39
|
+
...result
|
|
40
|
+
});
|
|
41
|
+
totalNew += result.new;
|
|
42
|
+
totalModified += result.modified;
|
|
43
|
+
totalDeleted += result.deleted;
|
|
44
|
+
totalUnchanged += result.unchanged;
|
|
45
|
+
// Report directory summary
|
|
46
|
+
if (result.new > 0 || result.modified > 0 || result.deleted > 0) {
|
|
47
|
+
callbacks?.onLog?.(` New: ${result.new}, Modified: ${result.modified}, Deleted: ${result.deleted}, Unchanged: ${result.unchanged}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
callbacks?.onLog?.(` All ${result.unchanged} records are up to date`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
summary: {
|
|
55
|
+
new: totalNew,
|
|
56
|
+
modified: totalModified,
|
|
57
|
+
deleted: totalDeleted,
|
|
58
|
+
unchanged: totalUnchanged
|
|
59
|
+
},
|
|
60
|
+
details
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async checkEntityDirectory(entityDir, entityConfig, callbacks) {
|
|
64
|
+
const result = { new: 0, modified: 0, deleted: 0, unchanged: 0 };
|
|
65
|
+
// Find files matching the configured pattern
|
|
66
|
+
const pattern = entityConfig.filePattern || '*.json';
|
|
67
|
+
const jsonFiles = await (0, fast_glob_1.default)(pattern, {
|
|
68
|
+
cwd: entityDir,
|
|
69
|
+
ignore: ['.mj-sync.json', '.mj-folder.json', '**/*.backup'],
|
|
70
|
+
dot: true // Include dotfiles (files starting with .)
|
|
71
|
+
});
|
|
72
|
+
for (const file of jsonFiles) {
|
|
73
|
+
try {
|
|
74
|
+
const filePath = path_1.default.join(entityDir, file);
|
|
75
|
+
const recordData = await fs_extra_1.default.readJson(filePath);
|
|
76
|
+
if (recordData.primaryKey) {
|
|
77
|
+
// Check if record exists in database
|
|
78
|
+
const entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);
|
|
79
|
+
if (!entity) {
|
|
80
|
+
result.deleted++;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Check if modified
|
|
84
|
+
const currentChecksum = this.syncEngine.calculateChecksum(recordData.fields);
|
|
85
|
+
if (recordData.sync?.checksum !== currentChecksum) {
|
|
86
|
+
result.modified++;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
result.unchanged++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// New record
|
|
95
|
+
result.new++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
callbacks?.onWarn?.(`Failed to check ${file}: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Recursively process subdirectories
|
|
103
|
+
const entries = await fs_extra_1.default.readdir(entityDir, { withFileTypes: true });
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
106
|
+
const subDir = path_1.default.join(entityDir, entry.name);
|
|
107
|
+
// Load subdirectory config and merge with parent config
|
|
108
|
+
let subEntityConfig = { ...entityConfig };
|
|
109
|
+
const subDirConfig = await (0, config_1.loadEntityConfig)(subDir);
|
|
110
|
+
if (subDirConfig) {
|
|
111
|
+
// Check if this is a new entity type (has different entity name)
|
|
112
|
+
if (subDirConfig.entity && subDirConfig.entity !== entityConfig.entity) {
|
|
113
|
+
// This is a different entity type, skip it (will be processed separately)
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Merge defaults: parent defaults + subdirectory overrides
|
|
117
|
+
subEntityConfig = {
|
|
118
|
+
...entityConfig,
|
|
119
|
+
...subDirConfig,
|
|
120
|
+
defaults: {
|
|
121
|
+
...entityConfig.defaults,
|
|
122
|
+
...(subDirConfig.defaults || {})
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Process subdirectory with merged config
|
|
127
|
+
const subResult = await this.checkEntityDirectory(subDir, subEntityConfig, callbacks);
|
|
128
|
+
result.new += subResult.new;
|
|
129
|
+
result.modified += subResult.modified;
|
|
130
|
+
result.deleted += subResult.deleted;
|
|
131
|
+
result.unchanged += subResult.unchanged;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.StatusService = StatusService;
|
|
138
|
+
//# sourceMappingURL=StatusService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusService.js","sourceRoot":"","sources":["../../src/services/StatusService.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\nimport fastGlob from 'fast-glob';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { loadEntityConfig } from '../config';\nimport { findEntityDirectories } from '../lib/provider-utils';\n\nexport interface StatusOptions {\n dir?: string;\n}\n\nexport interface StatusCallbacks {\n onProgress?: (message: string) => void;\n onLog?: (message: string) => void;\n onWarn?: (message: string) => void;\n}\n\nexport interface StatusResult {\n new: number;\n modified: number;\n deleted: number;\n unchanged: number;\n}\n\nexport interface EntityStatusResult {\n entityName: string;\n directory: string;\n new: number;\n modified: number;\n deleted: number;\n unchanged: number;\n}\n\nexport class StatusService {\n private syncEngine: SyncEngine;\n \n constructor(syncEngine: SyncEngine) {\n this.syncEngine = syncEngine;\n }\n \n async checkStatus(options: StatusOptions, callbacks?: StatusCallbacks): Promise<{\n summary: StatusResult;\n details: EntityStatusResult[];\n }> {\n const entityDirs = findEntityDirectories(process.cwd(), options.dir);\n \n if (entityDirs.length === 0) {\n throw new Error('No entity directories found');\n }\n \n callbacks?.onLog?.(`Found ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} to check`);\n \n const details: EntityStatusResult[] = [];\n let totalNew = 0;\n let totalModified = 0;\n let totalDeleted = 0;\n let totalUnchanged = 0;\n \n for (const entityDir of entityDirs) {\n const entityConfig = await loadEntityConfig(entityDir);\n if (!entityConfig) {\n callbacks?.onWarn?.(`Skipping ${entityDir} - no valid entity configuration`);\n continue;\n }\n \n callbacks?.onLog?.(`Checking ${entityConfig.entity} in ${entityDir}`);\n \n const result = await this.checkEntityDirectory(\n entityDir,\n entityConfig,\n callbacks\n );\n \n details.push({\n entityName: entityConfig.entity,\n directory: entityDir,\n ...result\n });\n \n totalNew += result.new;\n totalModified += result.modified;\n totalDeleted += result.deleted;\n totalUnchanged += result.unchanged;\n \n // Report directory summary\n if (result.new > 0 || result.modified > 0 || result.deleted > 0) {\n callbacks?.onLog?.(` New: ${result.new}, Modified: ${result.modified}, Deleted: ${result.deleted}, Unchanged: ${result.unchanged}`);\n } else {\n callbacks?.onLog?.(` All ${result.unchanged} records are up to date`);\n }\n }\n \n return {\n summary: {\n new: totalNew,\n modified: totalModified,\n deleted: totalDeleted,\n unchanged: totalUnchanged\n },\n details\n };\n }\n \n private async checkEntityDirectory(\n entityDir: string,\n entityConfig: any,\n callbacks?: StatusCallbacks\n ): Promise<StatusResult> {\n const result: StatusResult = { new: 0, modified: 0, deleted: 0, unchanged: 0 };\n \n // Find files matching the configured pattern\n const pattern = entityConfig.filePattern || '*.json';\n const jsonFiles = await fastGlob(pattern, {\n cwd: entityDir,\n ignore: ['.mj-sync.json', '.mj-folder.json', '**/*.backup'],\n dot: true // Include dotfiles (files starting with .)\n });\n \n for (const file of jsonFiles) {\n try {\n const filePath = path.join(entityDir, file);\n const recordData: RecordData = await fs.readJson(filePath);\n \n if (recordData.primaryKey) {\n // Check if record exists in database\n const entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);\n \n if (!entity) {\n result.deleted++;\n } else {\n // Check if modified\n const currentChecksum = this.syncEngine.calculateChecksum(recordData.fields);\n if (recordData.sync?.checksum !== currentChecksum) {\n result.modified++;\n } else {\n result.unchanged++;\n }\n }\n } else {\n // New record\n result.new++;\n }\n \n } catch (error) {\n callbacks?.onWarn?.(`Failed to check ${file}: ${error}`);\n }\n }\n \n // Recursively process subdirectories\n const entries = await fs.readdir(entityDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('.')) {\n const subDir = path.join(entityDir, entry.name);\n \n // Load subdirectory config and merge with parent config\n let subEntityConfig = { ...entityConfig };\n const subDirConfig = await loadEntityConfig(subDir);\n \n if (subDirConfig) {\n // Check if this is a new entity type (has different entity name)\n if (subDirConfig.entity && subDirConfig.entity !== entityConfig.entity) {\n // This is a different entity type, skip it (will be processed separately)\n continue;\n }\n \n // Merge defaults: parent defaults + subdirectory overrides\n subEntityConfig = {\n ...entityConfig,\n ...subDirConfig,\n defaults: {\n ...entityConfig.defaults,\n ...(subDirConfig.defaults || {})\n }\n };\n }\n \n // Process subdirectory with merged config\n const subResult = await this.checkEntityDirectory(\n subDir,\n subEntityConfig,\n callbacks\n );\n \n result.new += subResult.new;\n result.modified += subResult.modified;\n result.deleted += subResult.deleted;\n result.unchanged += subResult.unchanged;\n }\n }\n \n return result;\n }\n}"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { BaseEntity } from '@memberjunction/core';
|
|
3
|
+
import { SyncEngine } from '../lib/sync-engine';
|
|
4
|
+
export interface WatchOptions {
|
|
5
|
+
dir?: string;
|
|
6
|
+
debounceMs?: number;
|
|
7
|
+
ignorePatterns?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface WatchCallbacks {
|
|
10
|
+
onFileAdd?: (filePath: string, entityDir: string, entityConfig: any) => void;
|
|
11
|
+
onFileChange?: (filePath: string, entityDir: string, entityConfig: any) => void;
|
|
12
|
+
onFileDelete?: (filePath: string, entityDir: string, entityConfig: any) => void;
|
|
13
|
+
onLog?: (message: string) => void;
|
|
14
|
+
onWarn?: (message: string) => void;
|
|
15
|
+
onError?: (error: Error) => void;
|
|
16
|
+
onRecordCreated?: (entity: BaseEntity, entityConfig: any) => void;
|
|
17
|
+
onRecordUpdated?: (entity: BaseEntity, changes: any, entityConfig: any) => void;
|
|
18
|
+
onRecordSaved?: (entity: BaseEntity, isNew: boolean, entityConfig: any) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface WatchResult {
|
|
21
|
+
watchers: chokidar.FSWatcher[];
|
|
22
|
+
stop: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare class WatchService {
|
|
25
|
+
private syncEngine;
|
|
26
|
+
private debounceTimers;
|
|
27
|
+
constructor(syncEngine: SyncEngine);
|
|
28
|
+
watch(options?: WatchOptions, callbacks?: WatchCallbacks): Promise<WatchResult>;
|
|
29
|
+
private handleFileChange;
|
|
30
|
+
private syncJsonFile;
|
|
31
|
+
private syncExternalFile;
|
|
32
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WatchService = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const provider_utils_1 = require("../lib/provider-utils");
|
|
12
|
+
class WatchService {
|
|
13
|
+
syncEngine;
|
|
14
|
+
debounceTimers = new Map();
|
|
15
|
+
constructor(syncEngine) {
|
|
16
|
+
this.syncEngine = syncEngine;
|
|
17
|
+
}
|
|
18
|
+
async watch(options = {}, callbacks) {
|
|
19
|
+
const entityDirs = (0, provider_utils_1.findEntityDirectories)(process.cwd(), options.dir);
|
|
20
|
+
if (entityDirs.length === 0) {
|
|
21
|
+
throw new Error('No entity directories found');
|
|
22
|
+
}
|
|
23
|
+
callbacks?.onLog?.(`Watching ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} for changes`);
|
|
24
|
+
// Set up watchers
|
|
25
|
+
const watchers = [];
|
|
26
|
+
for (const entityDir of entityDirs) {
|
|
27
|
+
const entityConfig = await (0, config_1.loadEntityConfig)(entityDir);
|
|
28
|
+
if (!entityConfig) {
|
|
29
|
+
callbacks?.onWarn?.(`Skipping ${entityDir} - no valid entity configuration`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
callbacks?.onLog?.(`Watching ${entityConfig.entity} in ${entityDir}`);
|
|
33
|
+
// Watch for JSON files and external files
|
|
34
|
+
const patterns = [
|
|
35
|
+
path_1.default.join(entityDir, entityConfig.filePattern || '**/*.json'),
|
|
36
|
+
path_1.default.join(entityDir, '**/*.md'),
|
|
37
|
+
path_1.default.join(entityDir, '**/*.txt'),
|
|
38
|
+
path_1.default.join(entityDir, '**/*.html'),
|
|
39
|
+
path_1.default.join(entityDir, '**/*.liquid'),
|
|
40
|
+
path_1.default.join(entityDir, '**/*.sql')
|
|
41
|
+
];
|
|
42
|
+
const ignored = [
|
|
43
|
+
'**/node_modules/**',
|
|
44
|
+
'**/.git/**',
|
|
45
|
+
'**/.mj-sync.json',
|
|
46
|
+
'**/.mj-folder.json',
|
|
47
|
+
'**/*.backup',
|
|
48
|
+
...(options.ignorePatterns || [])
|
|
49
|
+
];
|
|
50
|
+
const watcher = chokidar_1.default.watch(patterns, {
|
|
51
|
+
ignored,
|
|
52
|
+
persistent: true,
|
|
53
|
+
ignoreInitial: true
|
|
54
|
+
});
|
|
55
|
+
watcher
|
|
56
|
+
.on('add', (filePath) => {
|
|
57
|
+
callbacks?.onFileAdd?.(filePath, entityDir, entityConfig);
|
|
58
|
+
this.handleFileChange(filePath, 'added', entityDir, entityConfig, options, callbacks);
|
|
59
|
+
})
|
|
60
|
+
.on('change', (filePath) => {
|
|
61
|
+
callbacks?.onFileChange?.(filePath, entityDir, entityConfig);
|
|
62
|
+
this.handleFileChange(filePath, 'changed', entityDir, entityConfig, options, callbacks);
|
|
63
|
+
})
|
|
64
|
+
.on('unlink', (filePath) => {
|
|
65
|
+
callbacks?.onFileDelete?.(filePath, entityDir, entityConfig);
|
|
66
|
+
this.handleFileChange(filePath, 'deleted', entityDir, entityConfig, options, callbacks);
|
|
67
|
+
});
|
|
68
|
+
watchers.push(watcher);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
watchers,
|
|
72
|
+
stop: async () => {
|
|
73
|
+
// Clear all debounce timers
|
|
74
|
+
for (const timer of this.debounceTimers.values()) {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
this.debounceTimers.clear();
|
|
78
|
+
// Close all watchers
|
|
79
|
+
await Promise.all(watchers.map(w => w.close()));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
handleFileChange(filePath, event, entityDir, entityConfig, options, callbacks) {
|
|
84
|
+
// Clear existing debounce timer
|
|
85
|
+
const existingTimer = this.debounceTimers.get(filePath);
|
|
86
|
+
if (existingTimer) {
|
|
87
|
+
clearTimeout(existingTimer);
|
|
88
|
+
}
|
|
89
|
+
// Set new debounce timer
|
|
90
|
+
const debounceMs = options.debounceMs || 1000;
|
|
91
|
+
const timer = setTimeout(async () => {
|
|
92
|
+
this.debounceTimers.delete(filePath);
|
|
93
|
+
try {
|
|
94
|
+
const relativePath = path_1.default.relative(entityDir, filePath);
|
|
95
|
+
callbacks?.onLog?.(`File ${event}: ${relativePath}`);
|
|
96
|
+
if (event === 'deleted') {
|
|
97
|
+
// Handle deletion
|
|
98
|
+
callbacks?.onLog?.('File deletion detected - manual database cleanup may be required');
|
|
99
|
+
}
|
|
100
|
+
else if (filePath.endsWith('.json')) {
|
|
101
|
+
// Handle JSON file change
|
|
102
|
+
await this.syncJsonFile(filePath, entityDir, entityConfig, callbacks);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Handle external file change
|
|
106
|
+
await this.syncExternalFile(filePath, entityDir, entityConfig, callbacks);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const errorMessage = `Failed to sync ${filePath}: ${error.message || error}`;
|
|
111
|
+
callbacks?.onWarn?.(errorMessage);
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
callbacks?.onError?.(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}, debounceMs);
|
|
117
|
+
this.debounceTimers.set(filePath, timer);
|
|
118
|
+
}
|
|
119
|
+
async syncJsonFile(filePath, entityDir, entityConfig, callbacks) {
|
|
120
|
+
const recordData = await fs_extra_1.default.readJson(filePath);
|
|
121
|
+
// Build defaults
|
|
122
|
+
const defaults = await this.syncEngine.buildDefaults(filePath, entityConfig);
|
|
123
|
+
// Load or create entity
|
|
124
|
+
let entity = null;
|
|
125
|
+
let isNew = false;
|
|
126
|
+
if (recordData.primaryKey) {
|
|
127
|
+
entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);
|
|
128
|
+
}
|
|
129
|
+
if (!entity) {
|
|
130
|
+
// New record
|
|
131
|
+
entity = await this.syncEngine.createEntityObject(entityConfig.entity);
|
|
132
|
+
entity.NewRecord();
|
|
133
|
+
isNew = true;
|
|
134
|
+
}
|
|
135
|
+
// Apply defaults first
|
|
136
|
+
for (const [field, value] of Object.entries(defaults)) {
|
|
137
|
+
if (field in entity) {
|
|
138
|
+
entity[field] = value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Apply record fields
|
|
142
|
+
for (const [field, value] of Object.entries(recordData.fields)) {
|
|
143
|
+
if (field in entity) {
|
|
144
|
+
const processedValue = await this.syncEngine.processFieldValue(value, path_1.default.dirname(filePath));
|
|
145
|
+
entity[field] = processedValue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Check if the record is dirty before saving
|
|
149
|
+
let wasActuallyUpdated = false;
|
|
150
|
+
let changes = null;
|
|
151
|
+
if (!isNew && entity.Dirty) {
|
|
152
|
+
// Record is dirty, get the changes
|
|
153
|
+
changes = entity.GetChangesSinceLastSave();
|
|
154
|
+
const changeKeys = Object.keys(changes);
|
|
155
|
+
if (changeKeys.length > 0) {
|
|
156
|
+
wasActuallyUpdated = true;
|
|
157
|
+
// Get primary key info for display
|
|
158
|
+
const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);
|
|
159
|
+
const primaryKeyDisplay = [];
|
|
160
|
+
if (entityInfo) {
|
|
161
|
+
for (const pk of entityInfo.PrimaryKeys) {
|
|
162
|
+
primaryKeyDisplay.push(`${pk.Name}: ${entity.Get(pk.Name)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
callbacks?.onLog?.(`📝 Updating ${entityConfig.entity} record:`);
|
|
166
|
+
if (primaryKeyDisplay.length > 0) {
|
|
167
|
+
callbacks?.onLog?.(` Primary Key: ${primaryKeyDisplay.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
callbacks?.onLog?.(` Changes:`);
|
|
170
|
+
for (const fieldName of changeKeys) {
|
|
171
|
+
const field = entity.GetFieldByName(fieldName);
|
|
172
|
+
const oldValue = field ? field.OldValue : undefined;
|
|
173
|
+
const newValue = changes[fieldName];
|
|
174
|
+
callbacks?.onLog?.(` ${fieldName}: ${oldValue} → ${newValue}`);
|
|
175
|
+
}
|
|
176
|
+
callbacks?.onRecordUpdated?.(entity, changes, entityConfig);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (isNew) {
|
|
180
|
+
wasActuallyUpdated = true;
|
|
181
|
+
callbacks?.onRecordCreated?.(entity, entityConfig);
|
|
182
|
+
}
|
|
183
|
+
// Save the record
|
|
184
|
+
const saved = await entity.Save();
|
|
185
|
+
if (!saved) {
|
|
186
|
+
const message = entity.LatestResult?.Message;
|
|
187
|
+
if (message) {
|
|
188
|
+
throw new Error(`Failed to save record: ${message}`);
|
|
189
|
+
}
|
|
190
|
+
const errors = entity.LatestResult?.Errors?.map(err => typeof err === 'string' ? err : (err?.message || JSON.stringify(err)))?.join(', ') || 'Unknown error';
|
|
191
|
+
throw new Error(`Failed to save record: ${errors}`);
|
|
192
|
+
}
|
|
193
|
+
if (wasActuallyUpdated) {
|
|
194
|
+
callbacks?.onLog?.(`Successfully ${isNew ? 'created' : 'updated'} ${entityConfig.entity} record`);
|
|
195
|
+
callbacks?.onRecordSaved?.(entity, isNew, entityConfig);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
callbacks?.onLog?.(`No changes detected for ${entityConfig.entity} record - skipped update`);
|
|
199
|
+
}
|
|
200
|
+
// Update the local file with new primary key if created
|
|
201
|
+
if (isNew) {
|
|
202
|
+
const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);
|
|
203
|
+
if (entityInfo) {
|
|
204
|
+
const newPrimaryKey = {};
|
|
205
|
+
for (const pk of entityInfo.PrimaryKeys) {
|
|
206
|
+
newPrimaryKey[pk.Name] = entity.Get(pk.Name);
|
|
207
|
+
}
|
|
208
|
+
recordData.primaryKey = newPrimaryKey;
|
|
209
|
+
// Update sync metadata
|
|
210
|
+
recordData.sync = {
|
|
211
|
+
lastModified: new Date().toISOString(),
|
|
212
|
+
checksum: this.syncEngine.calculateChecksum(recordData.fields)
|
|
213
|
+
};
|
|
214
|
+
// Write back to file
|
|
215
|
+
await fs_extra_1.default.writeJson(filePath, recordData, { spaces: 2 });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async syncExternalFile(filePath, entityDir, entityConfig, callbacks) {
|
|
220
|
+
// Find the corresponding JSON file
|
|
221
|
+
const fileName = path_1.default.basename(filePath);
|
|
222
|
+
const parts = fileName.split('.');
|
|
223
|
+
if (parts.length >= 3) {
|
|
224
|
+
// Format: uuid.fieldname.ext
|
|
225
|
+
const jsonFileName = `${parts[0]}.json`;
|
|
226
|
+
const fieldName = parts[1];
|
|
227
|
+
const jsonFilePath = path_1.default.join(path_1.default.dirname(filePath), jsonFileName);
|
|
228
|
+
if (await fs_extra_1.default.pathExists(jsonFilePath)) {
|
|
229
|
+
// Update the JSON file's sync metadata to trigger a sync
|
|
230
|
+
const recordData = await fs_extra_1.default.readJson(jsonFilePath);
|
|
231
|
+
recordData.sync = {
|
|
232
|
+
lastModified: new Date().toISOString(),
|
|
233
|
+
checksum: recordData.sync?.checksum || ''
|
|
234
|
+
};
|
|
235
|
+
await fs_extra_1.default.writeJson(jsonFilePath, recordData, { spaces: 2 });
|
|
236
|
+
callbacks?.onLog?.(`Updated sync metadata for ${jsonFileName} due to external file change`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.WatchService = WatchService;
|
|
242
|
+
//# sourceMappingURL=WatchService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WatchService.js","sourceRoot":"","sources":["../../src/services/WatchService.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\nimport chokidar from 'chokidar';\nimport { BaseEntity } from '@memberjunction/core';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { loadEntityConfig } from '../config';\nimport { findEntityDirectories } from '../lib/provider-utils';\n\nexport interface WatchOptions {\n dir?: string;\n debounceMs?: number;\n ignorePatterns?: string[];\n}\n\nexport interface WatchCallbacks {\n onFileAdd?: (filePath: string, entityDir: string, entityConfig: any) => void;\n onFileChange?: (filePath: string, entityDir: string, entityConfig: any) => void;\n onFileDelete?: (filePath: string, entityDir: string, entityConfig: any) => void;\n onLog?: (message: string) => void;\n onWarn?: (message: string) => void;\n onError?: (error: Error) => void;\n onRecordCreated?: (entity: BaseEntity, entityConfig: any) => void;\n onRecordUpdated?: (entity: BaseEntity, changes: any, entityConfig: any) => void;\n onRecordSaved?: (entity: BaseEntity, isNew: boolean, entityConfig: any) => void;\n}\n\nexport interface WatchResult {\n watchers: chokidar.FSWatcher[];\n stop: () => Promise<void>;\n}\n\nexport class WatchService {\n private syncEngine: SyncEngine;\n private debounceTimers: Map<string, NodeJS.Timeout> = new Map();\n \n constructor(syncEngine: SyncEngine) {\n this.syncEngine = syncEngine;\n }\n \n async watch(options: WatchOptions = {}, callbacks?: WatchCallbacks): Promise<WatchResult> {\n const entityDirs = findEntityDirectories(process.cwd(), options.dir);\n \n if (entityDirs.length === 0) {\n throw new Error('No entity directories found');\n }\n \n callbacks?.onLog?.(`Watching ${entityDirs.length} entity ${entityDirs.length === 1 ? 'directory' : 'directories'} for changes`);\n \n // Set up watchers\n const watchers: chokidar.FSWatcher[] = [];\n \n for (const entityDir of entityDirs) {\n const entityConfig = await loadEntityConfig(entityDir);\n if (!entityConfig) {\n callbacks?.onWarn?.(`Skipping ${entityDir} - no valid entity configuration`);\n continue;\n }\n \n callbacks?.onLog?.(`Watching ${entityConfig.entity} in ${entityDir}`);\n \n // Watch for JSON files and external files\n const patterns = [\n path.join(entityDir, entityConfig.filePattern || '**/*.json'),\n path.join(entityDir, '**/*.md'),\n path.join(entityDir, '**/*.txt'),\n path.join(entityDir, '**/*.html'),\n path.join(entityDir, '**/*.liquid'),\n path.join(entityDir, '**/*.sql')\n ];\n \n const ignored = [\n '**/node_modules/**',\n '**/.git/**',\n '**/.mj-sync.json',\n '**/.mj-folder.json',\n '**/*.backup',\n ...(options.ignorePatterns || [])\n ];\n \n const watcher = chokidar.watch(patterns, {\n ignored,\n persistent: true,\n ignoreInitial: true\n });\n \n watcher\n .on('add', (filePath) => {\n callbacks?.onFileAdd?.(filePath, entityDir, entityConfig);\n this.handleFileChange(filePath, 'added', entityDir, entityConfig, options, callbacks);\n })\n .on('change', (filePath) => {\n callbacks?.onFileChange?.(filePath, entityDir, entityConfig);\n this.handleFileChange(filePath, 'changed', entityDir, entityConfig, options, callbacks);\n })\n .on('unlink', (filePath) => {\n callbacks?.onFileDelete?.(filePath, entityDir, entityConfig);\n this.handleFileChange(filePath, 'deleted', entityDir, entityConfig, options, callbacks);\n });\n \n watchers.push(watcher);\n }\n \n return {\n watchers,\n stop: async () => {\n // Clear all debounce timers\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer);\n }\n this.debounceTimers.clear();\n \n // Close all watchers\n await Promise.all(watchers.map(w => w.close()));\n }\n };\n }\n \n private handleFileChange(\n filePath: string,\n event: string,\n entityDir: string,\n entityConfig: any,\n options: WatchOptions,\n callbacks?: WatchCallbacks\n ): void {\n // Clear existing debounce timer\n const existingTimer = this.debounceTimers.get(filePath);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n \n // Set new debounce timer\n const debounceMs = options.debounceMs || 1000;\n const timer = setTimeout(async () => {\n this.debounceTimers.delete(filePath);\n \n try {\n const relativePath = path.relative(entityDir, filePath);\n callbacks?.onLog?.(`File ${event}: ${relativePath}`);\n \n if (event === 'deleted') {\n // Handle deletion\n callbacks?.onLog?.('File deletion detected - manual database cleanup may be required');\n } else if (filePath.endsWith('.json')) {\n // Handle JSON file change\n await this.syncJsonFile(filePath, entityDir, entityConfig, callbacks);\n } else {\n // Handle external file change\n await this.syncExternalFile(filePath, entityDir, entityConfig, callbacks);\n }\n } catch (error) {\n const errorMessage = `Failed to sync ${filePath}: ${(error as any).message || error}`;\n callbacks?.onWarn?.(errorMessage);\n if (error instanceof Error) {\n callbacks?.onError?.(error);\n }\n }\n }, debounceMs);\n \n this.debounceTimers.set(filePath, timer);\n }\n \n private async syncJsonFile(\n filePath: string,\n entityDir: string,\n entityConfig: any,\n callbacks?: WatchCallbacks\n ): Promise<void> {\n const recordData: RecordData = await fs.readJson(filePath);\n \n // Build defaults\n const defaults = await this.syncEngine.buildDefaults(filePath, entityConfig);\n \n // Load or create entity\n let entity: BaseEntity | null = null;\n let isNew = false;\n \n if (recordData.primaryKey) {\n entity = await this.syncEngine.loadEntity(entityConfig.entity, recordData.primaryKey);\n }\n \n if (!entity) {\n // New record\n entity = await this.syncEngine.createEntityObject(entityConfig.entity);\n entity.NewRecord();\n isNew = true;\n }\n \n // Apply defaults first\n for (const [field, value] of Object.entries(defaults)) {\n if (field in entity) {\n (entity as any)[field] = value;\n }\n }\n \n // Apply record fields\n for (const [field, value] of Object.entries(recordData.fields)) {\n if (field in entity) {\n const processedValue = await this.syncEngine.processFieldValue(value, path.dirname(filePath));\n (entity as any)[field] = processedValue;\n }\n }\n \n // Check if the record is dirty before saving\n let wasActuallyUpdated = false;\n let changes: any = null;\n \n if (!isNew && entity.Dirty) {\n // Record is dirty, get the changes\n changes = (entity as any).GetChangesSinceLastSave();\n const changeKeys = Object.keys(changes);\n if (changeKeys.length > 0) {\n wasActuallyUpdated = true;\n \n // Get primary key info for display\n const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);\n const primaryKeyDisplay: string[] = [];\n if (entityInfo) {\n for (const pk of entityInfo.PrimaryKeys) {\n primaryKeyDisplay.push(`${pk.Name}: ${entity.Get(pk.Name)}`);\n }\n }\n \n callbacks?.onLog?.(`📝 Updating ${entityConfig.entity} record:`);\n if (primaryKeyDisplay.length > 0) {\n callbacks?.onLog?.(` Primary Key: ${primaryKeyDisplay.join(', ')}`);\n }\n callbacks?.onLog?.(` Changes:`);\n for (const fieldName of changeKeys) {\n const field = entity.GetFieldByName(fieldName);\n const oldValue = field ? field.OldValue : undefined;\n const newValue = (changes as any)[fieldName];\n callbacks?.onLog?.(` ${fieldName}: ${oldValue} → ${newValue}`);\n }\n \n callbacks?.onRecordUpdated?.(entity, changes, entityConfig);\n }\n } else if (isNew) {\n wasActuallyUpdated = true;\n callbacks?.onRecordCreated?.(entity, entityConfig);\n }\n \n // Save the record\n const saved = await entity.Save();\n if (!saved) {\n const message = entity.LatestResult?.Message;\n if (message) {\n throw new Error(`Failed to save record: ${message}`);\n }\n \n const errors = entity.LatestResult?.Errors?.map(err => \n typeof err === 'string' ? err : (err?.message || JSON.stringify(err))\n )?.join(', ') || 'Unknown error';\n throw new Error(`Failed to save record: ${errors}`);\n }\n \n if (wasActuallyUpdated) {\n callbacks?.onLog?.(`Successfully ${isNew ? 'created' : 'updated'} ${entityConfig.entity} record`);\n callbacks?.onRecordSaved?.(entity, isNew, entityConfig);\n } else {\n callbacks?.onLog?.(`No changes detected for ${entityConfig.entity} record - skipped update`);\n }\n \n // Update the local file with new primary key if created\n if (isNew) {\n const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);\n if (entityInfo) {\n const newPrimaryKey: Record<string, any> = {};\n for (const pk of entityInfo.PrimaryKeys) {\n newPrimaryKey[pk.Name] = entity.Get(pk.Name);\n }\n recordData.primaryKey = newPrimaryKey;\n \n // Update sync metadata\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: this.syncEngine.calculateChecksum(recordData.fields)\n };\n \n // Write back to file\n await fs.writeJson(filePath, recordData, { spaces: 2 });\n }\n }\n }\n \n private async syncExternalFile(\n filePath: string,\n entityDir: string,\n entityConfig: any,\n callbacks?: WatchCallbacks\n ): Promise<void> {\n // Find the corresponding JSON file\n const fileName = path.basename(filePath);\n const parts = fileName.split('.');\n \n if (parts.length >= 3) {\n // Format: uuid.fieldname.ext\n const jsonFileName = `${parts[0]}.json`;\n const fieldName = parts[1];\n const jsonFilePath = path.join(path.dirname(filePath), jsonFileName);\n \n if (await fs.pathExists(jsonFilePath)) {\n // Update the JSON file's sync metadata to trigger a sync\n const recordData: RecordData = await fs.readJson(jsonFilePath);\n recordData.sync = {\n lastModified: new Date().toISOString(),\n checksum: recordData.sync?.checksum || ''\n };\n await fs.writeJson(jsonFilePath, recordData, { spaces: 2 });\n \n callbacks?.onLog?.(`Updated sync metadata for ${jsonFileName} due to external file change`);\n }\n }\n }\n}"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Service exports for MetadataSync library usage
|
|
3
|
+
* @module services
|
|
4
|
+
*
|
|
5
|
+
* This module exports all the service classes that can be used programmatically
|
|
6
|
+
* without the CLI interface. These services provide the core functionality
|
|
7
|
+
* for metadata synchronization operations.
|
|
8
|
+
*/
|
|
9
|
+
export { InitService, InitOptions, InitCallbacks } from './InitService';
|
|
10
|
+
export { PullService, PullOptions, PullCallbacks, PullResult } from './PullService';
|
|
11
|
+
export { PushService, PushOptions, PushCallbacks, PushResult, EntityPushResult } from './PushService';
|
|
12
|
+
export { ValidationService } from './ValidationService';
|
|
13
|
+
export { FormattingService } from './FormattingService';
|
|
14
|
+
export { StatusService, StatusOptions, StatusCallbacks, StatusResult, EntityStatusResult } from './StatusService';
|
|
15
|
+
export { FileResetService, FileResetOptions, FileResetCallbacks, FileResetResult } from './FileResetService';
|
|
16
|
+
export { WatchService, WatchOptions, WatchCallbacks, WatchResult } from './WatchService';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Service exports for MetadataSync library usage
|
|
4
|
+
* @module services
|
|
5
|
+
*
|
|
6
|
+
* This module exports all the service classes that can be used programmatically
|
|
7
|
+
* without the CLI interface. These services provide the core functionality
|
|
8
|
+
* for metadata synchronization operations.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.WatchService = exports.FileResetService = exports.StatusService = exports.FormattingService = exports.ValidationService = exports.PushService = exports.PullService = exports.InitService = void 0;
|
|
12
|
+
var InitService_1 = require("./InitService");
|
|
13
|
+
Object.defineProperty(exports, "InitService", { enumerable: true, get: function () { return InitService_1.InitService; } });
|
|
14
|
+
var PullService_1 = require("./PullService");
|
|
15
|
+
Object.defineProperty(exports, "PullService", { enumerable: true, get: function () { return PullService_1.PullService; } });
|
|
16
|
+
var PushService_1 = require("./PushService");
|
|
17
|
+
Object.defineProperty(exports, "PushService", { enumerable: true, get: function () { return PushService_1.PushService; } });
|
|
18
|
+
var ValidationService_1 = require("./ValidationService");
|
|
19
|
+
Object.defineProperty(exports, "ValidationService", { enumerable: true, get: function () { return ValidationService_1.ValidationService; } });
|
|
20
|
+
var FormattingService_1 = require("./FormattingService");
|
|
21
|
+
Object.defineProperty(exports, "FormattingService", { enumerable: true, get: function () { return FormattingService_1.FormattingService; } });
|
|
22
|
+
var StatusService_1 = require("./StatusService");
|
|
23
|
+
Object.defineProperty(exports, "StatusService", { enumerable: true, get: function () { return StatusService_1.StatusService; } });
|
|
24
|
+
var FileResetService_1 = require("./FileResetService");
|
|
25
|
+
Object.defineProperty(exports, "FileResetService", { enumerable: true, get: function () { return FileResetService_1.FileResetService; } });
|
|
26
|
+
var WatchService_1 = require("./WatchService");
|
|
27
|
+
Object.defineProperty(exports, "WatchService", { enumerable: true, get: function () { return WatchService_1.WatchService; } });
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,6CAAwE;AAA/D,0GAAA,WAAW,OAAA;AACpB,6CAAoF;AAA3E,0GAAA,WAAW,OAAA;AACpB,6CAAsG;AAA7F,0GAAA,WAAW,OAAA;AACpB,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,iDAAkH;AAAzG,8GAAA,aAAa,OAAA;AACtB,uDAA6G;AAApG,oHAAA,gBAAgB,OAAA;AACzB,+CAAyF;AAAhF,4GAAA,YAAY,OAAA","sourcesContent":["/**\n * @fileoverview Service exports for MetadataSync library usage\n * @module services\n * \n * This module exports all the service classes that can be used programmatically\n * without the CLI interface. These services provide the core functionality\n * for metadata synchronization operations.\n */\n\nexport { InitService, InitOptions, InitCallbacks } from './InitService';\nexport { PullService, PullOptions, PullCallbacks, PullResult } from './PullService';\nexport { PushService, PushOptions, PushCallbacks, PushResult, EntityPushResult } from './PushService';\nexport { ValidationService } from './ValidationService';\nexport { FormattingService } from './FormattingService';\nexport { StatusService, StatusOptions, StatusCallbacks, StatusResult, EntityStatusResult } from './StatusService';\nexport { FileResetService, FileResetOptions, FileResetCallbacks, FileResetResult } from './FileResetService';\nexport { WatchService, WatchOptions, WatchCallbacks, WatchResult } from './WatchService';"]}
|