@thinkwise/testwise 0.2.0-beta.3 → 0.2.5
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/Testwise.ts +8 -7
- package/artifact-builder/ArtifactManager.ts +0 -1
- package/artifact-builder/InterfaceGenerator.ts +73 -83
- package/artifact-builder/ModelDataBuilder.ts +74 -86
- package/artifact-builder/ModelDataRefiner.ts +18 -13
- package/artifact-builder/SchemaGenerator.ts +1 -4
- package/artifact-builder/ScreenInterfaceRefiner.ts +0 -5
- package/artifact-builder/SelectorBuilder.ts +8 -1
- package/artifact-builder/SubjectComponentGenerator.ts +77 -7
- package/artifact-builder/SubjectGenerator.ts +3 -3
- package/artifact-builder/SubjectRegistration.ts +69 -61
- package/artifact-builder/helpers/DataRetriever.ts +6 -3
- package/artifact-builder/helpers/NamingHandler.ts +28 -20
- package/artifact-builder/helpers/Stopwatch.ts +13 -0
- package/artifact-builder/helpers/index.ts +1 -0
- package/components/BaseComponentObjects.ts +4 -1
- package/components/action-bar/ActionBar.ts +3 -3
- package/components/grid/Grid.ts +18 -21
- package/components/tab/BaseTab.ts +2 -2
- package/components/tab/BaseTabObjects.ts +1 -1
- package/components/tab/DetailTabPage.ts +2 -2
- package/components/tab/Tab.ts +4 -4
- package/controls/LookupDropdown.ts +7 -2
- package/dist/Testwise.d.ts +1 -0
- package/dist/Testwise.js +15 -6
- package/dist/Testwise.js.map +1 -1
- package/dist/artifact-builder/ArtifactManager.js.map +1 -1
- package/dist/artifact-builder/InterfaceGenerator.d.ts +1 -1
- package/dist/artifact-builder/InterfaceGenerator.js +60 -67
- package/dist/artifact-builder/InterfaceGenerator.js.map +1 -1
- package/dist/artifact-builder/ModelDataBuilder.js +49 -60
- package/dist/artifact-builder/ModelDataBuilder.js.map +1 -1
- package/dist/artifact-builder/ModelDataRefiner.js +11 -7
- package/dist/artifact-builder/ModelDataRefiner.js.map +1 -1
- package/dist/artifact-builder/SchemaGenerator.js +0 -2
- package/dist/artifact-builder/SchemaGenerator.js.map +1 -1
- package/dist/artifact-builder/ScreenInterfaceRefiner.js +0 -5
- package/dist/artifact-builder/ScreenInterfaceRefiner.js.map +1 -1
- package/dist/artifact-builder/SelectorBuilder.js +3 -1
- package/dist/artifact-builder/SelectorBuilder.js.map +1 -1
- package/dist/artifact-builder/SubjectComponentGenerator.d.ts +4 -0
- package/dist/artifact-builder/SubjectComponentGenerator.js +61 -5
- package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -1
- package/dist/artifact-builder/SubjectGenerator.js +1 -1
- package/dist/artifact-builder/SubjectGenerator.js.map +1 -1
- package/dist/artifact-builder/SubjectRegistration.d.ts +10 -11
- package/dist/artifact-builder/SubjectRegistration.js +52 -44
- package/dist/artifact-builder/SubjectRegistration.js.map +1 -1
- package/dist/artifact-builder/helpers/DataRetriever.js +4 -3
- package/dist/artifact-builder/helpers/DataRetriever.js.map +1 -1
- package/dist/artifact-builder/helpers/NamingHandler.d.ts +2 -1
- package/dist/artifact-builder/helpers/NamingHandler.js +23 -16
- package/dist/artifact-builder/helpers/NamingHandler.js.map +1 -1
- package/dist/artifact-builder/helpers/Stopwatch.d.ts +5 -0
- package/dist/artifact-builder/helpers/Stopwatch.js +13 -0
- package/dist/artifact-builder/helpers/Stopwatch.js.map +1 -0
- package/dist/artifact-builder/helpers/index.d.ts +1 -0
- package/dist/artifact-builder/helpers/index.js +1 -0
- package/dist/artifact-builder/helpers/index.js.map +1 -1
- package/dist/components/BaseComponentObjects.d.ts +1 -0
- package/dist/components/BaseComponentObjects.js +3 -1
- package/dist/components/BaseComponentObjects.js.map +1 -1
- package/dist/components/action-bar/ActionBar.d.ts +1 -1
- package/dist/components/action-bar/ActionBar.js +3 -3
- package/dist/components/grid/Grid.d.ts +1 -0
- package/dist/components/grid/Grid.js +4 -1
- package/dist/components/grid/Grid.js.map +1 -1
- package/dist/components/tab/BaseTab.d.ts +2 -2
- package/dist/components/tab/BaseTab.js +2 -2
- package/dist/components/tab/BaseTab.js.map +1 -1
- package/dist/components/tab/BaseTabObjects.js +1 -1
- package/dist/components/tab/BaseTabObjects.js.map +1 -1
- package/dist/components/tab/DetailTabPage.d.ts +2 -2
- package/dist/components/tab/DetailTabPage.js +2 -2
- package/dist/components/tab/DetailTabPage.js.map +1 -1
- package/dist/components/tab/Tab.d.ts +3 -3
- package/dist/components/tab/Tab.js +4 -4
- package/dist/components/tab/Tab.js.map +1 -1
- package/dist/controls/LookupDropdown.d.ts +3 -7
- package/dist/controls/LookupDropdown.js.map +1 -1
- package/dist/enums/ElementTypes.d.ts +1 -1
- package/dist/enums/ElementTypes.js +1 -1
- package/dist/enums/ElementTypes.js.map +1 -1
- package/dist/helpers/ConfigChecker.d.ts +3 -0
- package/dist/helpers/ConfigChecker.js +7 -0
- package/dist/helpers/ConfigChecker.js.map +1 -0
- package/dist/helpers/LoginHelper.js +1 -1
- package/dist/helpers/LoginHelper.js.map +1 -1
- package/dist/interfaces/IComponentObjects.d.ts +1 -0
- package/dist/page-extensions/SubjectRegistry.d.ts +0 -8
- package/dist/page-extensions/SubjectRegistry.js +2 -6
- package/dist/page-extensions/SubjectRegistry.js.map +1 -1
- package/dist/page-extensions/index.d.ts +0 -1
- package/dist/page-extensions/index.js +0 -1
- package/dist/page-extensions/index.js.map +1 -1
- package/dist/services/IndiciumApi.service.d.ts +27 -0
- package/dist/services/IndiciumApi.service.js +135 -0
- package/dist/services/IndiciumApi.service.js.map +1 -0
- package/dist/templates/test-artifacts/SubjectPageBase.d.ts +5 -0
- package/dist/templates/test-artifacts/SubjectPageBase.js +6 -0
- package/dist/templates/test-artifacts/SubjectPageBase.js.map +1 -0
- package/dist/templates/test-artifacts/screens/index.d.ts +1 -0
- package/dist/templates/test-artifacts/screens/index.js +2 -0
- package/dist/templates/test-artifacts/screens/index.js.map +1 -0
- package/dist/templates/test-artifacts/subjects/index.d.ts +1 -0
- package/dist/templates/test-artifacts/subjects/index.js +2 -0
- package/dist/templates/test-artifacts/subjects/index.js.map +1 -0
- package/enums/ElementTypes.ts +2 -2
- package/helpers/ConfigChecker.ts +7 -0
- package/helpers/LoginHelper.ts +1 -1
- package/interfaces/IComponentObjects.ts +1 -0
- package/interfaces/IRegisteredSubjects.ts +1 -1
- package/package.json +5 -3
- package/page-extensions/SubjectRegistry.ts +2 -19
- package/page-extensions/index.ts +0 -1
- package/scripts/main.js +63 -82
- package/scripts/postinstall.js +40 -42
- package/scripts/setup.js +37 -37
- package/scripts/sync.js +726 -39
- package/services/ConfigBuilder.ts +1 -1
- package/services/IndiciumApi.service.ts +159 -0
- package/templates/SubjectRegistry.template.ts +73 -0
- package/templates/test-artifacts/SubjectPageBase.ts +9 -0
- package/templates/test-artifacts/screens/index.ts +0 -0
- package/templates/test-artifacts/subjects/index.ts +0 -0
- package/tsconfig.json +2 -3
- package/types/Components.ts +1 -1
- package/dist/config.json +0 -10
- package/dist/page-extensions/SubjectProvider.d.ts +0 -11
- package/dist/page-extensions/SubjectProvider.js +0 -24
- package/dist/page-extensions/SubjectProvider.js.map +0 -1
- package/dist/test-artifacts/index.d.ts +0 -3
- package/dist/test-artifacts/index.js +0 -4
- package/dist/test-artifacts/index.js.map +0 -1
- package/page-extensions/SubjectProvider.ts +0 -41
- package/test-artifacts/index.ts +0 -3
package/scripts/sync.js
CHANGED
|
@@ -1,69 +1,756 @@
|
|
|
1
|
-
|
|
2
1
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
3
2
|
|
|
4
3
|
import { execSync } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import process from 'node:process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import {
|
|
8
|
+
import { PathResolver } from '../dist/helpers/index.js';
|
|
9
|
+
import { indiciumApi } from '../dist/services/IndiciumApi.service.js';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Constants & Configuration
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
/** @type {Readonly<{ROOT: string, DIST: string, SEED_DATA: string, TEST_ARTIFACTS: string, TEMP_TEST_ARTIFACTS: string, TEMPLATE_TEST_ARTIFACTS: string}>} */
|
|
19
|
+
const PATHS = Object.freeze({
|
|
20
|
+
ROOT: path.resolve(__dirname, '..'),
|
|
21
|
+
DIST: path.resolve(__dirname, '..', 'dist'),
|
|
22
|
+
SEED_DATA: path.resolve(__dirname, '..', 'seed-data'),
|
|
23
|
+
TEST_ARTIFACTS: path.resolve(__dirname, '..', 'test-artifacts'),
|
|
24
|
+
TEMP_TEST_ARTIFACTS: path.resolve(__dirname, '..', 'test-artifacts-temp'),
|
|
25
|
+
TEMPLATE_TEST_ARTIFACTS: path.resolve(__dirname, '..', 'templates', 'test-artifacts')
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** @type {readonly string[]} */
|
|
29
|
+
const REQUIRED_SEED_FILES = Object.freeze(['screensToBuild.json', 'subjectsToBuild.json']);
|
|
30
|
+
|
|
31
|
+
/** @type {Readonly<{CLEANUP: string, BUILD: string, REFINE: string, GENERATE: string, COMPILE: string, BACKUP: string}>} */
|
|
32
|
+
const PHASE_NAMES = Object.freeze({
|
|
33
|
+
CLEANUP: 'Cleanup and Initial Compilation',
|
|
34
|
+
BUILD: 'Build Model Data',
|
|
35
|
+
REFINE: 'Refine Model Data',
|
|
36
|
+
GENERATE: 'Generate Artifacts',
|
|
37
|
+
COMPILE: 'Final Compilation',
|
|
38
|
+
BACKUP: 'Backup'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** @type {Readonly<{INFO: string, SUCCESS: string, ERROR: string, WARNING: string, PHASE: string, STEP: string, DIVIDER: string}>} */
|
|
42
|
+
const LOG_SYMBOLS = Object.freeze({
|
|
43
|
+
INFO: 'ℹ️',
|
|
44
|
+
SUCCESS: '✅',
|
|
45
|
+
ERROR: '❌',
|
|
46
|
+
WARNING: '⚠️',
|
|
47
|
+
PHASE: '📦',
|
|
48
|
+
STEP: '▸',
|
|
49
|
+
DIVIDER: '═'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Types & Interfaces
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} SyncContext
|
|
58
|
+
* @property {import('../dist/artifact-builder/helpers/index.js').Stopwatch} stopwatch - Stopwatch instance for timing
|
|
59
|
+
* @property {ReturnType<import('../dist/artifact-builder/helpers/index.js').Stopwatch['start']>} totalTimer - Total elapsed timer
|
|
60
|
+
* @property {AbortSignal} [signal] - Optional abort signal for graceful shutdown
|
|
61
|
+
* @property {SyncOptions} options - Sync configuration options
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @typedef {Object} SyncOptions
|
|
66
|
+
* @property {boolean} [dryRun=false] - If true, only logs what would happen without executing
|
|
67
|
+
* @property {boolean} [verbose=false] - If true, enables verbose logging
|
|
68
|
+
* @property {boolean} [skipBackup=false] - If true, skips the backup phase
|
|
69
|
+
* @property {number} [retryAttempts=0] - Number of retry attempts for failed operations
|
|
70
|
+
* @property {number} [retryDelay=1000] - Delay in ms between retry attempts
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {Object} StepResult
|
|
75
|
+
* @property {boolean} success - Whether the step succeeded
|
|
76
|
+
* @property {number} duration - Duration in milliseconds
|
|
77
|
+
* @property {Error} [error] - Error if step failed
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Logger Utility
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Centralized logging utility with consistent formatting.
|
|
86
|
+
*/
|
|
87
|
+
const Logger = {
|
|
88
|
+
/**
|
|
89
|
+
* Logs an informational message.
|
|
90
|
+
* @param {string} message - The message to log
|
|
91
|
+
*/
|
|
92
|
+
info(message) {
|
|
93
|
+
console.info(`${LOG_SYMBOLS.INFO} ${message}`);
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Logs a success message.
|
|
98
|
+
* @param {string} message - The message to log
|
|
99
|
+
*/
|
|
100
|
+
success(message) {
|
|
101
|
+
console.info(`${LOG_SYMBOLS.SUCCESS} ${message}`);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Logs an error message.
|
|
106
|
+
* @param {string} message - The message to log
|
|
107
|
+
* @param {Error} [error] - Optional error object
|
|
108
|
+
*/
|
|
109
|
+
error(message, error) {
|
|
110
|
+
console.error(`${LOG_SYMBOLS.ERROR} ${message}`);
|
|
111
|
+
if (error?.stack) {
|
|
112
|
+
console.error('\nStack trace:');
|
|
113
|
+
console.error(error.stack);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Logs a warning message.
|
|
119
|
+
* @param {string} message - The message to log
|
|
120
|
+
*/
|
|
121
|
+
warn(message) {
|
|
122
|
+
console.warn(`${LOG_SYMBOLS.WARNING} ${message}`);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Logs a phase header.
|
|
127
|
+
* @param {number} phaseNumber - The phase number
|
|
128
|
+
* @param {string} phaseName - The phase name
|
|
129
|
+
*/
|
|
130
|
+
phase(phaseNumber, phaseName) {
|
|
131
|
+
console.info(`\n${LOG_SYMBOLS.PHASE} Phase ${phaseNumber}: ${phaseName}\n`);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Logs a step execution.
|
|
136
|
+
* @param {string} stepName - The step name
|
|
137
|
+
*/
|
|
138
|
+
step(stepName) {
|
|
139
|
+
console.info(` ${LOG_SYMBOLS.STEP} ${stepName}`);
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Logs a divider line.
|
|
144
|
+
* @param {number} [width=60] - The width of the divider
|
|
145
|
+
*/
|
|
146
|
+
divider(width = 60) {
|
|
147
|
+
console.info(LOG_SYMBOLS.DIVIDER.repeat(width));
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Logs a header with dividers.
|
|
152
|
+
* @param {string} message - The header message
|
|
153
|
+
*/
|
|
154
|
+
header(message) {
|
|
155
|
+
this.divider();
|
|
156
|
+
console.info(message);
|
|
157
|
+
this.divider();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Utility Functions
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sleeps for the specified duration.
|
|
167
|
+
* @param {number} ms - Duration in milliseconds
|
|
168
|
+
* @returns {Promise<void>}
|
|
169
|
+
*/
|
|
170
|
+
function sleep(ms) {
|
|
171
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Executes a function with retry logic.
|
|
176
|
+
* @template T
|
|
177
|
+
* @param {() => Promise<T> | T} fn - The function to execute
|
|
178
|
+
* @param {number} [attempts=3] - Number of retry attempts
|
|
179
|
+
* @param {number} [delay=1000] - Delay between retries in ms
|
|
180
|
+
* @returns {Promise<T>}
|
|
181
|
+
*/
|
|
182
|
+
async function withRetry(fn, attempts = 3, delay = 1000) {
|
|
183
|
+
let lastError;
|
|
184
|
+
|
|
185
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
186
|
+
try {
|
|
187
|
+
return await fn();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
lastError = error;
|
|
190
|
+
if (attempt < attempts) {
|
|
191
|
+
Logger.warn(`Attempt ${attempt}/${attempts} failed, retrying in ${delay}ms...`);
|
|
192
|
+
await sleep(delay);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
throw lastError;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Safely cleans a directory without deleting the root folder
|
|
202
|
+
* to avoid EPERM errors during npm postinstall.
|
|
203
|
+
* @param {string} dirPath - The directory path to clean
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
206
|
+
function safeCleanDir(dirPath) {
|
|
207
|
+
if (!fs.existsSync(dirPath)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Rename test-artifacts to temp directory if needed (only once)
|
|
212
|
+
if (fs.existsSync(PATHS.TEST_ARTIFACTS) && !fs.existsSync(PATHS.TEMP_TEST_ARTIFACTS)) {
|
|
213
|
+
fs.renameSync(PATHS.TEST_ARTIFACTS, PATHS.TEMP_TEST_ARTIFACTS);
|
|
214
|
+
fs.cpSync(PATHS.TEMPLATE_TEST_ARTIFACTS, PATHS.TEST_ARTIFACTS, { recursive: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const files = fs.readdirSync(dirPath);
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const filePath = path.join(dirPath, file);
|
|
220
|
+
try {
|
|
221
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
222
|
+
} catch (error) {
|
|
223
|
+
Logger.warn(`Could not remove ${filePath}: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Ensures a JSON file exists to prevent ENOENT crashes
|
|
230
|
+
* if the Refiner doesn't handle missing files gracefully.
|
|
231
|
+
* @param {string} filePath - The file path to ensure exists
|
|
232
|
+
* @param {unknown} [defaultContent=[]] - Default content to write if file doesn't exist
|
|
233
|
+
* @returns {void}
|
|
234
|
+
*/
|
|
235
|
+
function ensureJsonExists(filePath, defaultContent = []) {
|
|
236
|
+
if (fs.existsSync(filePath)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const dir = path.dirname(filePath);
|
|
241
|
+
if (!fs.existsSync(dir)) {
|
|
242
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
243
|
+
}
|
|
244
|
+
fs.writeFileSync(filePath, JSON.stringify(defaultContent, null, 2));
|
|
245
|
+
}
|
|
9
246
|
|
|
10
|
-
|
|
11
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Validates that all required dependencies and directories exist.
|
|
249
|
+
* @throws {Error} If validation fails
|
|
250
|
+
* @returns {void}
|
|
251
|
+
*/
|
|
252
|
+
function validateEnvironment() {
|
|
253
|
+
// Ensure seed-data directory exists
|
|
254
|
+
if (!fs.existsSync(PATHS.SEED_DATA)) {
|
|
255
|
+
fs.mkdirSync(PATHS.SEED_DATA, { recursive: true });
|
|
256
|
+
}
|
|
12
257
|
|
|
13
|
-
|
|
258
|
+
// Verify TypeScript config exists
|
|
259
|
+
const tsconfigPath = path.join(PATHS.ROOT, 'tsconfig.json');
|
|
260
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
261
|
+
throw new Error('tsconfig.json not found. Please ensure the project is properly set up.');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
14
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Executes a TypeScript compilation.
|
|
267
|
+
* @param {string} cwd - The working directory
|
|
268
|
+
* @throws {Error} If compilation fails
|
|
269
|
+
* @returns {void}
|
|
270
|
+
*/
|
|
271
|
+
function compileTypeScript(cwd) {
|
|
15
272
|
try {
|
|
16
|
-
|
|
273
|
+
execSync('npx tsc', { cwd, stdio: 'inherit' });
|
|
274
|
+
} catch (error) {
|
|
275
|
+
throw new Error(`TypeScript compilation failed: ${error.message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Resets the SubjectRegistry.ts file from template.
|
|
281
|
+
* @returns {void}
|
|
282
|
+
*/
|
|
283
|
+
function resetSubjectRegistry() {
|
|
284
|
+
const registryPath = path.join(PATHS.ROOT, 'page-extensions', 'SubjectRegistry.ts');
|
|
285
|
+
const templatePath = path.join(PATHS.ROOT, 'templates', 'SubjectRegistry.template.ts');
|
|
286
|
+
|
|
287
|
+
if (fs.existsSync(registryPath)) {
|
|
288
|
+
fs.rmSync(registryPath, { force: true });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (fs.existsSync(templatePath)) {
|
|
292
|
+
fs.copyFileSync(templatePath, registryPath);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Cleans up temporary test artifacts directory.
|
|
298
|
+
* @returns {void}
|
|
299
|
+
*/
|
|
300
|
+
function cleanupTempArtifacts() {
|
|
301
|
+
if (fs.existsSync(PATHS.TEMP_TEST_ARTIFACTS)) {
|
|
302
|
+
fs.rmSync(PATHS.TEMP_TEST_ARTIFACTS, { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
17
305
|
|
|
18
|
-
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// Sync Pipeline Steps
|
|
308
|
+
// ============================================================================
|
|
19
309
|
|
|
20
|
-
|
|
21
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Runs a single sync step with timing and optional dry-run support.
|
|
312
|
+
* @param {SyncContext} ctx - The sync context
|
|
313
|
+
* @param {string} stepName - Name of the step
|
|
314
|
+
* @param {() => Promise<void> | void} fn - The function to execute
|
|
315
|
+
* @returns {Promise<StepResult>}
|
|
316
|
+
*/
|
|
317
|
+
async function runStep(ctx, stepName, fn) {
|
|
318
|
+
Logger.step(stepName);
|
|
22
319
|
|
|
23
|
-
|
|
24
|
-
|
|
320
|
+
if (ctx.options.dryRun) {
|
|
321
|
+
return { success: true, duration: 0 };
|
|
322
|
+
}
|
|
25
323
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// const { SubjectRegistration } = await import('../dist/artifact-builder/index.js'); - do not remove: future feature
|
|
324
|
+
const timer = ctx.stopwatch.start(stepName);
|
|
325
|
+
const startTime = Date.now();
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
// Check for abort signal
|
|
329
|
+
if (ctx.signal?.aborted) {
|
|
330
|
+
throw new Error('Sync process was aborted');
|
|
331
|
+
}
|
|
35
332
|
|
|
36
|
-
|
|
333
|
+
const execute =
|
|
334
|
+
ctx.options.retryAttempts > 0 ? () => withRetry(fn, ctx.options.retryAttempts, ctx.options.retryDelay) : fn;
|
|
335
|
+
|
|
336
|
+
await execute();
|
|
337
|
+
|
|
338
|
+
return { success: true, duration: Date.now() - startTime };
|
|
339
|
+
} catch (error) {
|
|
340
|
+
return { success: false, duration: Date.now() - startTime, error };
|
|
341
|
+
} finally {
|
|
342
|
+
timer.stop();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Phase 1: Cleanup and initial compilation
|
|
348
|
+
* @param {SyncContext} ctx - The sync context
|
|
349
|
+
* @returns {Promise<void>}
|
|
350
|
+
*/
|
|
351
|
+
async function phaseCleanupAndCompile(ctx) {
|
|
352
|
+
Logger.phase(1, PHASE_NAMES.CLEANUP);
|
|
353
|
+
|
|
354
|
+
resetSubjectRegistry();
|
|
355
|
+
|
|
356
|
+
const cleanResult = await runStep(ctx, 'Clear distribution directory', () => {
|
|
357
|
+
safeCleanDir(PATHS.DIST);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (!cleanResult.success) {
|
|
361
|
+
throw cleanResult.error;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const compileResult = await runStep(ctx, 'Recompile TypeScript sources', () => {
|
|
365
|
+
compileTypeScript(PATHS.ROOT);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (!compileResult.success) {
|
|
369
|
+
throw compileResult.error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Phase 2: Build model data
|
|
375
|
+
* @param {SyncContext} ctx - The sync context
|
|
376
|
+
* @returns {Promise<void>}
|
|
377
|
+
*/
|
|
378
|
+
async function phaseBuildModelData(ctx) {
|
|
379
|
+
Logger.phase(2, PHASE_NAMES.BUILD);
|
|
380
|
+
|
|
381
|
+
const { buildSubjects, buildScreens } = await import('../dist/artifact-builder/ModelDataBuilder.js');
|
|
382
|
+
|
|
383
|
+
const subjectsResult = await runStep(ctx, 'Build subjects from model data', async () => {
|
|
37
384
|
await buildSubjects();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
if (!subjectsResult.success) {
|
|
388
|
+
throw subjectsResult.error;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const screensResult = await runStep(ctx, 'Build screens from model data', async () => {
|
|
38
392
|
await buildScreens();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (!screensResult.success) {
|
|
396
|
+
throw screensResult.error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
39
399
|
|
|
40
|
-
|
|
400
|
+
/**
|
|
401
|
+
* Phase 3: Refine model data
|
|
402
|
+
* @param {SyncContext} ctx - The sync context
|
|
403
|
+
* @returns {Promise<void>}
|
|
404
|
+
*/
|
|
405
|
+
async function phaseRefineModelData(ctx) {
|
|
406
|
+
Logger.phase(3, PHASE_NAMES.REFINE);
|
|
407
|
+
|
|
408
|
+
// Ensure required seed files exist before refinement
|
|
409
|
+
for (const seedFile of REQUIRED_SEED_FILES) {
|
|
410
|
+
ensureJsonExists(path.join(PATHS.SEED_DATA, seedFile));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const { ModelDataRefiner } = await import('../dist/artifact-builder/index.js');
|
|
414
|
+
|
|
415
|
+
const refineResult = await runStep(ctx, 'Refine model data', async () => {
|
|
41
416
|
await new ModelDataRefiner().run();
|
|
42
|
-
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (!refineResult.success) {
|
|
420
|
+
throw refineResult.error;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Phase 4: Generate artifacts
|
|
426
|
+
* @param {SyncContext} ctx - The sync context
|
|
427
|
+
* @returns {Promise<void>}
|
|
428
|
+
*/
|
|
429
|
+
async function phaseGenerateArtifacts(ctx) {
|
|
430
|
+
Logger.phase(4, PHASE_NAMES.GENERATE);
|
|
431
|
+
|
|
432
|
+
const {
|
|
433
|
+
InterfaceGenerator,
|
|
434
|
+
ScreenInterfaceRefiner,
|
|
435
|
+
SchemaGenerator,
|
|
436
|
+
SubjectComponentGenerator,
|
|
437
|
+
SubjectGenerator,
|
|
438
|
+
SubjectRegistration
|
|
439
|
+
} = await import('../dist/artifact-builder/index.js');
|
|
440
|
+
|
|
441
|
+
const interfaceGenerator = new InterfaceGenerator();
|
|
43
442
|
|
|
44
|
-
|
|
45
|
-
|
|
443
|
+
/** @type {Array<{name: string, fn: () => Promise<void> | void}>} */
|
|
444
|
+
const steps = [
|
|
445
|
+
{
|
|
446
|
+
name: 'Generate screen interfaces from screen schemas',
|
|
447
|
+
fn: () => interfaceGenerator.generateScreenInterfacesFromSchemas()
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: 'Refine interfaces',
|
|
451
|
+
fn: () => new ScreenInterfaceRefiner().refine()
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: 'Generate subject component schemas',
|
|
455
|
+
fn: () => new SchemaGenerator().generateSchema()
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: 'Generate subject component interfaces from schemas',
|
|
459
|
+
fn: () => interfaceGenerator.generateSubjectInterfacesFromSchemas()
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: 'Build components that implement interfaces',
|
|
463
|
+
fn: () => new SubjectComponentGenerator().run()
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: 'Generate subject classes',
|
|
467
|
+
fn: () => new SubjectGenerator().run()
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'Add subjects to registry',
|
|
471
|
+
fn: async () => await new SubjectRegistration().run()
|
|
472
|
+
}
|
|
473
|
+
];
|
|
46
474
|
|
|
47
|
-
|
|
475
|
+
for (const step of steps) {
|
|
476
|
+
const result = await runStep(ctx, step.name, step.fn);
|
|
477
|
+
if (!result.success) {
|
|
478
|
+
throw result.error;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Phase 5: Final compilation
|
|
485
|
+
* @param {SyncContext} ctx - The sync context
|
|
486
|
+
* @returns {Promise<void>}
|
|
487
|
+
*/
|
|
488
|
+
async function phaseFinalCompilation(ctx) {
|
|
489
|
+
Logger.phase(5, PHASE_NAMES.COMPILE);
|
|
48
490
|
|
|
49
|
-
|
|
50
|
-
|
|
491
|
+
const cleanResult = await runStep(ctx, 'Clear distribution directory', () => {
|
|
492
|
+
safeCleanDir(PATHS.DIST);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
if (!cleanResult.success) {
|
|
496
|
+
throw cleanResult.error;
|
|
497
|
+
}
|
|
51
498
|
|
|
52
|
-
|
|
499
|
+
const compileResult = await runStep(ctx, 'Compile generated artifacts', () => {
|
|
500
|
+
compileTypeScript(PATHS.ROOT);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
if (!compileResult.success) {
|
|
504
|
+
throw compileResult.error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Phase 6: Backup
|
|
510
|
+
* @param {SyncContext} ctx - The sync context
|
|
511
|
+
* @returns {Promise<void>}
|
|
512
|
+
*/
|
|
513
|
+
async function phaseBackup(ctx) {
|
|
514
|
+
if (ctx.options.skipBackup) {
|
|
515
|
+
Logger.info('Skipping backup phase (--skip-backup flag)');
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
Logger.phase(6, PHASE_NAMES.BACKUP);
|
|
520
|
+
|
|
521
|
+
const { ArtifactManager } = await import('../dist/artifact-builder/index.js');
|
|
522
|
+
|
|
523
|
+
const backupResult = await runStep(ctx, 'Perform backup', () => {
|
|
524
|
+
new ArtifactManager().performBackup();
|
|
525
|
+
cleanupTempArtifacts();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
if (!backupResult.success) {
|
|
529
|
+
throw backupResult.error;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ============================================================================
|
|
534
|
+
// Main Sync Function
|
|
535
|
+
// ============================================================================
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Parses command line arguments into sync options.
|
|
539
|
+
* @returns {SyncOptions}
|
|
540
|
+
*/
|
|
541
|
+
function parseCommandLineArgs() {
|
|
542
|
+
const args = process.argv.slice(2);
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
dryRun: args.includes('--dry-run'),
|
|
546
|
+
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
547
|
+
skipBackup: args.includes('--skip-backup'),
|
|
548
|
+
retryAttempts: parseInt(args.find((arg) => arg.startsWith('--retry='))?.split('=')[1] ?? '0', 10),
|
|
549
|
+
retryDelay: parseInt(args.find((arg) => arg.startsWith('--retry-delay='))?.split('=')[1] ?? '1000', 10)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Sets up graceful shutdown handlers.
|
|
555
|
+
* @param {AbortController} controller - The abort controller
|
|
556
|
+
* @returns {void}
|
|
557
|
+
*/
|
|
558
|
+
function setupShutdownHandlers(controller) {
|
|
559
|
+
const shutdown = () => {
|
|
560
|
+
Logger.warn('Received shutdown signal, attempting graceful shutdown...');
|
|
561
|
+
controller.abort();
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
process.on('SIGINT', shutdown);
|
|
565
|
+
process.on('SIGTERM', shutdown);
|
|
566
|
+
}
|
|
53
567
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
568
|
+
/**
|
|
569
|
+
* Executes the complete synchronization process.
|
|
570
|
+
*
|
|
571
|
+
* This process:
|
|
572
|
+
* 1. Cleans the dist directory and compiles TypeScript
|
|
573
|
+
* 2. Builds model data from subjects and screens
|
|
574
|
+
* 3. Refines the model data
|
|
575
|
+
* 4. Generates all artifacts (interfaces, schemas, components, subjects)
|
|
576
|
+
* 5. Performs final compilation
|
|
577
|
+
* 6. Creates a backup
|
|
578
|
+
*
|
|
579
|
+
* @param {SyncOptions} [options={}] - Optional sync configuration
|
|
580
|
+
* @returns {Promise<void>}
|
|
581
|
+
* @throws {Error} If sync process fails
|
|
582
|
+
*/
|
|
583
|
+
export async function sync(options = {}) {
|
|
584
|
+
// Merge provided options with command line args (CLI takes precedence)
|
|
585
|
+
const cliOptions = parseCommandLineArgs();
|
|
586
|
+
const mergedOptions = { ...options, ...cliOptions };
|
|
57
587
|
|
|
58
|
-
|
|
59
|
-
|
|
588
|
+
// Setup abort controller for graceful shutdown
|
|
589
|
+
const abortController = new AbortController();
|
|
590
|
+
setupShutdownHandlers(abortController);
|
|
60
591
|
|
|
61
|
-
|
|
592
|
+
// Late import to avoid circular dependency issues during initial compile
|
|
593
|
+
const { Stopwatch } = await import('../dist/artifact-builder/helpers/index.js');
|
|
594
|
+
|
|
595
|
+
const stopwatch = new Stopwatch();
|
|
596
|
+
const totalTimer = stopwatch.start('Sync process start to end');
|
|
597
|
+
|
|
598
|
+
/** @type {SyncContext} */
|
|
599
|
+
const ctx = {
|
|
600
|
+
stopwatch,
|
|
601
|
+
totalTimer,
|
|
602
|
+
signal: abortController.signal,
|
|
603
|
+
options: mergedOptions
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
Logger.header('🚀 Starting Testwise Sync Process');
|
|
607
|
+
|
|
608
|
+
if (mergedOptions.dryRun) {
|
|
609
|
+
Logger.warn('Running in DRY RUN mode - no changes will be made');
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (mergedOptions.verbose) {
|
|
613
|
+
Logger.info(`Options: ${JSON.stringify(mergedOptions, null, 2)}`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Model data verification before any sync steps
|
|
617
|
+
if (!(await verifiedModelData())) {
|
|
618
|
+
Logger.info('Model data verification failed. Sync aborted.');
|
|
619
|
+
process.exit(0);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
// Pre-flight validation
|
|
624
|
+
validateEnvironment();
|
|
625
|
+
|
|
626
|
+
// Execute phases in sequence
|
|
627
|
+
await phaseCleanupAndCompile(ctx);
|
|
628
|
+
await phaseBuildModelData(ctx);
|
|
629
|
+
await phaseRefineModelData(ctx);
|
|
630
|
+
await phaseGenerateArtifacts(ctx);
|
|
631
|
+
await phaseFinalCompilation(ctx);
|
|
632
|
+
|
|
633
|
+
Logger.header('✅ Sync process completed successfully!');
|
|
62
634
|
} catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
635
|
+
Logger.divider();
|
|
636
|
+
Logger.error('Sync process failed!', error);
|
|
637
|
+
Logger.divider();
|
|
638
|
+
process.exit(1);
|
|
66
639
|
} finally {
|
|
67
|
-
|
|
640
|
+
try {
|
|
641
|
+
await phaseBackup(ctx);
|
|
642
|
+
} catch (backupError) {
|
|
643
|
+
Logger.warn(`Backup failed: ${backupError.message}`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
totalTimer.stop();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Verify that model data is properly configured
|
|
652
|
+
* @returns {Promise<boolean>} True if model data is verified, false otherwise
|
|
653
|
+
*/
|
|
654
|
+
async function verifiedModelData() {
|
|
655
|
+
try {
|
|
656
|
+
const consumerRoot = new PathResolver().getConsumerRootDirectory();
|
|
657
|
+
const seedDataDir = path.join(consumerRoot, 'seed-data');
|
|
658
|
+
const subjectsJson = path.join(seedDataDir, 'subjects.json');
|
|
659
|
+
const screensDir = path.join(seedDataDir, 'screen-schemas');
|
|
660
|
+
const projectIsConfigured = await isConfigured();
|
|
661
|
+
|
|
662
|
+
// Check if seed-data directory exists
|
|
663
|
+
if (!fs.existsSync(seedDataDir)) {
|
|
664
|
+
return handleMissingSeedDataDirectory(seedDataDir, screensDir, projectIsConfigured);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Check if subjects.json exists
|
|
668
|
+
if (!fs.existsSync(subjectsJson) && !projectIsConfigured) {
|
|
669
|
+
Logger.info('Required file not found: subjects.json');
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Check if screens directory exists
|
|
674
|
+
if (!fs.existsSync(screensDir)) {
|
|
675
|
+
return handleMissingScreensDirectory(screensDir, projectIsConfigured);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Check if there are any screen files
|
|
679
|
+
const screenFiles = fs.readdirSync(screensDir).filter((f) => f.endsWith('.json'));
|
|
680
|
+
if (screenFiles.length === 0 && !projectIsConfigured) {
|
|
681
|
+
Logger.info('No screen JSON files found in seed-data/screen-schemas');
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return true;
|
|
686
|
+
} catch (error) {
|
|
687
|
+
Logger.error('Model data verification error:', error);
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Handle missing seed-data directory
|
|
694
|
+
* @param {string} seedDataDir - Path to seed-data directory
|
|
695
|
+
* @param {string} screensDir - Path to screens directory
|
|
696
|
+
* @param {boolean} projectIsConfigured - Whether project is configured
|
|
697
|
+
* @returns {boolean} True if handled successfully, false otherwise
|
|
698
|
+
*/
|
|
699
|
+
function handleMissingSeedDataDirectory(seedDataDir, screensDir, projectIsConfigured) {
|
|
700
|
+
if (projectIsConfigured) {
|
|
701
|
+
try {
|
|
702
|
+
fs.mkdirSync(seedDataDir, { recursive: true });
|
|
703
|
+
fs.mkdirSync(screensDir, { recursive: true });
|
|
704
|
+
Logger.info('Created seed-data directory structure.');
|
|
705
|
+
return true;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
Logger.error('Failed to create seed-data directory:', error);
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
Logger.info('No seed-data directory found.');
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Handle missing screens directory
|
|
718
|
+
* @param {string} screensDir - Path to screens directory
|
|
719
|
+
* @param {boolean} projectIsConfigured - Whether project is configured
|
|
720
|
+
* @returns {boolean} True if handled successfully, false otherwise
|
|
721
|
+
*/
|
|
722
|
+
function handleMissingScreensDirectory(screensDir, projectIsConfigured) {
|
|
723
|
+
if (projectIsConfigured) {
|
|
724
|
+
try {
|
|
725
|
+
fs.mkdirSync(screensDir, { recursive: true });
|
|
726
|
+
Logger.info('Created screen-schemas directory.');
|
|
727
|
+
return true;
|
|
728
|
+
} catch (error) {
|
|
729
|
+
Logger.error('Failed to create screen-schemas directory:', error);
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
Logger.info('No screen-schemas directory found.');
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Check if project is configured with valid credentials
|
|
740
|
+
* @returns {Promise<boolean>} True if project is configured, false otherwise
|
|
741
|
+
*/
|
|
742
|
+
async function isConfigured() {
|
|
743
|
+
try {
|
|
744
|
+
if (typeof indiciumApi.canConnect === 'function') {
|
|
745
|
+
return await indiciumApi.canConnect();
|
|
746
|
+
}
|
|
747
|
+
if (typeof indiciumApi.canConnectToProject === 'function') {
|
|
748
|
+
return await indiciumApi.canConnectToProject();
|
|
749
|
+
}
|
|
750
|
+
Logger.warn('No valid connection check found on indiciumApi.');
|
|
751
|
+
return false;
|
|
752
|
+
} catch (error) {
|
|
753
|
+
Logger.error('Configuration check failed:', error);
|
|
754
|
+
return false;
|
|
68
755
|
}
|
|
69
756
|
}
|