@openmrs/esm-implementer-tools-app 8.0.1-pre.3406 → 8.0.1-pre.3415
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/.turbo/turbo-build.log +2 -2
- package/dist/3629.js +1 -1
- package/dist/3629.js.map +1 -1
- package/dist/5563.js +1 -1
- package/dist/5563.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-implementer-tools-app.js +1 -1
- package/dist/openmrs-esm-implementer-tools-app.js.buildmanifest.json +10 -10
- package/dist/routes.json +1 -1
- package/package.json +3 -3
- package/src/backend-dependencies/openmrs-backend-dependencies.test.ts +434 -0
- package/src/backend-dependencies/openmrs-backend-dependencies.ts +55 -12
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { openmrsFetch, isVersionSatisfied } from '@openmrs/esm-framework';
|
|
2
|
+
import {
|
|
3
|
+
checkModules,
|
|
4
|
+
hasInvalidDependencies,
|
|
5
|
+
clearCache,
|
|
6
|
+
type ResolvedBackendModuleType,
|
|
7
|
+
} from './openmrs-backend-dependencies';
|
|
8
|
+
|
|
9
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
10
|
+
openmrsFetch: jest.fn(),
|
|
11
|
+
isVersionSatisfied: jest.fn(),
|
|
12
|
+
restBaseUrl: '/ws/rest/v1',
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
16
|
+
const mockIsVersionSatisfied = jest.mocked(isVersionSatisfied);
|
|
17
|
+
|
|
18
|
+
describe('openmrs-backend-dependencies', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
clearCache();
|
|
22
|
+
window.installedModules = [];
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('checkModules', () => {
|
|
26
|
+
it('should return empty array when no modules have backend dependencies', async () => {
|
|
27
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
28
|
+
data: {
|
|
29
|
+
results: [],
|
|
30
|
+
links: [],
|
|
31
|
+
},
|
|
32
|
+
} as any);
|
|
33
|
+
|
|
34
|
+
window.installedModules = [
|
|
35
|
+
['@openmrs/esm-app-1', {}],
|
|
36
|
+
['@openmrs/esm-app-2', {} as any],
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const result = await checkModules();
|
|
40
|
+
|
|
41
|
+
expect(result).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should identify missing backend modules', async () => {
|
|
45
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
46
|
+
data: {
|
|
47
|
+
results: [{ uuid: 'webservices.rest', version: '2.24.0' }],
|
|
48
|
+
links: [],
|
|
49
|
+
},
|
|
50
|
+
} as any);
|
|
51
|
+
|
|
52
|
+
window.installedModules = [['@openmrs/esm-test-app', { backendDependencies: { 'missing-module': '1.0.0' } }]];
|
|
53
|
+
|
|
54
|
+
const result = await checkModules();
|
|
55
|
+
|
|
56
|
+
expect(result).toHaveLength(1);
|
|
57
|
+
expect(result[0].name).toBe('@openmrs/esm-test-app');
|
|
58
|
+
expect(result[0].dependencies).toHaveLength(1);
|
|
59
|
+
expect(result[0].dependencies[0]).toMatchObject({
|
|
60
|
+
name: 'missing-module',
|
|
61
|
+
requiredVersion: '1.0.0',
|
|
62
|
+
type: 'missing',
|
|
63
|
+
});
|
|
64
|
+
expect(result[0].dependencies[0].installedVersion).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should identify version mismatches', async () => {
|
|
68
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
69
|
+
data: {
|
|
70
|
+
results: [{ uuid: 'webservices.rest', version: '2.24.0' }],
|
|
71
|
+
links: [],
|
|
72
|
+
},
|
|
73
|
+
} as any);
|
|
74
|
+
|
|
75
|
+
mockIsVersionSatisfied.mockReturnValue(false);
|
|
76
|
+
|
|
77
|
+
window.installedModules = [['@openmrs/esm-test-app', { backendDependencies: { 'webservices.rest': '^3.0.0' } }]];
|
|
78
|
+
|
|
79
|
+
const result = await checkModules();
|
|
80
|
+
|
|
81
|
+
expect(result[0].dependencies[0]).toMatchObject({
|
|
82
|
+
name: 'webservices.rest',
|
|
83
|
+
requiredVersion: '^3.0.0',
|
|
84
|
+
installedVersion: '2.24.0',
|
|
85
|
+
type: 'version-mismatch',
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should mark satisfied dependencies as okay', async () => {
|
|
90
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
91
|
+
data: {
|
|
92
|
+
results: [{ uuid: 'webservices.rest', version: '2.24.0' }],
|
|
93
|
+
links: [],
|
|
94
|
+
},
|
|
95
|
+
} as any);
|
|
96
|
+
|
|
97
|
+
mockIsVersionSatisfied.mockReturnValue(true);
|
|
98
|
+
|
|
99
|
+
window.installedModules = [['@openmrs/esm-test-app', { backendDependencies: { 'webservices.rest': '^2.0.0' } }]];
|
|
100
|
+
|
|
101
|
+
const result = await checkModules();
|
|
102
|
+
|
|
103
|
+
expect(result[0].dependencies[0]).toMatchObject({
|
|
104
|
+
name: 'webservices.rest',
|
|
105
|
+
requiredVersion: '^2.0.0',
|
|
106
|
+
installedVersion: '2.24.0',
|
|
107
|
+
type: 'okay',
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle multiple modules with mixed dependency states', async () => {
|
|
112
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
113
|
+
data: {
|
|
114
|
+
results: [
|
|
115
|
+
{ uuid: 'webservices.rest', version: '2.24.0' },
|
|
116
|
+
{ uuid: 'fhir2', version: '1.5.0' },
|
|
117
|
+
],
|
|
118
|
+
links: [],
|
|
119
|
+
},
|
|
120
|
+
} as any);
|
|
121
|
+
|
|
122
|
+
mockIsVersionSatisfied.mockImplementation((required, installed) => {
|
|
123
|
+
if (required === '^2.0.0' && installed === '2.24.0') return true;
|
|
124
|
+
if (required === '^1.0.0' && installed === '1.5.0') return true;
|
|
125
|
+
return false;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
window.installedModules = [
|
|
129
|
+
[
|
|
130
|
+
'@openmrs/esm-app-1',
|
|
131
|
+
{
|
|
132
|
+
backendDependencies: {
|
|
133
|
+
'webservices.rest': '^2.0.0',
|
|
134
|
+
'missing-module': '1.0.0',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
['@openmrs/esm-app-2', { backendDependencies: { fhir2: '^1.0.0' } }],
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const result = await checkModules();
|
|
142
|
+
|
|
143
|
+
expect(result).toHaveLength(2);
|
|
144
|
+
expect(result[0].dependencies).toHaveLength(2);
|
|
145
|
+
expect(result[0].dependencies.find((d) => d.type === 'okay')).toBeDefined();
|
|
146
|
+
expect(result[0].dependencies.find((d) => d.type === 'missing')).toBeDefined();
|
|
147
|
+
expect(result[1].dependencies).toHaveLength(1);
|
|
148
|
+
expect(result[1].dependencies[0].type).toBe('okay');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should include installed optional dependencies with required ones', async () => {
|
|
152
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
153
|
+
data: {
|
|
154
|
+
results: [
|
|
155
|
+
{ uuid: 'webservices.rest', version: '2.24.0' },
|
|
156
|
+
{ uuid: 'fhir2', version: '1.5.0' },
|
|
157
|
+
],
|
|
158
|
+
links: [],
|
|
159
|
+
},
|
|
160
|
+
} as any);
|
|
161
|
+
|
|
162
|
+
mockIsVersionSatisfied.mockReturnValue(true);
|
|
163
|
+
|
|
164
|
+
window.installedModules = [
|
|
165
|
+
[
|
|
166
|
+
'@openmrs/esm-test-app',
|
|
167
|
+
{
|
|
168
|
+
backendDependencies: { 'webservices.rest': '^2.0.0' },
|
|
169
|
+
optionalBackendDependencies: {
|
|
170
|
+
fhir2: '^1.0.0',
|
|
171
|
+
'optional-missing': '^1.0.0',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const result = await checkModules();
|
|
178
|
+
|
|
179
|
+
expect(result[0].dependencies).toHaveLength(2);
|
|
180
|
+
// Should include both required and installed optional
|
|
181
|
+
expect(result[0].dependencies.find((d) => d.name === 'webservices.rest')).toBeDefined();
|
|
182
|
+
expect(result[0].dependencies.find((d) => d.name === 'fhir2')).toBeDefined();
|
|
183
|
+
// Should not include missing optional dependencies
|
|
184
|
+
expect(result[0].dependencies.find((d) => d.name === 'optional-missing')).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should extract version from optional dependencies with feature flags', async () => {
|
|
188
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
189
|
+
data: {
|
|
190
|
+
results: [
|
|
191
|
+
{ uuid: 'webservices.rest', version: '2.24.0' },
|
|
192
|
+
{ uuid: 'fhir2', version: '1.5.0' },
|
|
193
|
+
],
|
|
194
|
+
links: [],
|
|
195
|
+
},
|
|
196
|
+
} as any);
|
|
197
|
+
|
|
198
|
+
mockIsVersionSatisfied.mockReturnValue(true);
|
|
199
|
+
|
|
200
|
+
window.installedModules = [
|
|
201
|
+
[
|
|
202
|
+
'@openmrs/esm-test-app',
|
|
203
|
+
{
|
|
204
|
+
backendDependencies: { 'webservices.rest': '^2.0.0' },
|
|
205
|
+
optionalBackendDependencies: {
|
|
206
|
+
fhir2: {
|
|
207
|
+
version: '^1.0.0',
|
|
208
|
+
feature: 'fhir-support' as any,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const result = await checkModules();
|
|
216
|
+
|
|
217
|
+
const fhir2Dep = result[0].dependencies.find((d) => d.name === 'fhir2');
|
|
218
|
+
expect(fhir2Dep).toBeDefined();
|
|
219
|
+
expect(fhir2Dep?.requiredVersion).toBe('^1.0.0');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should cache results across multiple calls', async () => {
|
|
223
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
224
|
+
data: {
|
|
225
|
+
results: [{ uuid: 'webservices.rest', version: '2.24.0' }],
|
|
226
|
+
links: [],
|
|
227
|
+
},
|
|
228
|
+
} as any);
|
|
229
|
+
|
|
230
|
+
mockIsVersionSatisfied.mockReturnValue(true);
|
|
231
|
+
|
|
232
|
+
window.installedModules = [['@openmrs/esm-test-app', { backendDependencies: { 'webservices.rest': '^2.0.0' } }]];
|
|
233
|
+
|
|
234
|
+
const result1 = await checkModules();
|
|
235
|
+
const result2 = await checkModules();
|
|
236
|
+
|
|
237
|
+
// Should only fetch once due to caching
|
|
238
|
+
expect(mockOpenmrsFetch).toHaveBeenCalledTimes(1);
|
|
239
|
+
expect(result1).toBe(result2); // Same reference
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should handle paginated backend module responses', async () => {
|
|
243
|
+
const page1Modules = Array.from({ length: 50 }, (_, i) => ({
|
|
244
|
+
uuid: `module-${i}`,
|
|
245
|
+
version: '1.0.0',
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
const page2Modules = [{ uuid: 'module-50', version: '1.0.0' }];
|
|
249
|
+
|
|
250
|
+
mockOpenmrsFetch
|
|
251
|
+
.mockResolvedValueOnce({
|
|
252
|
+
data: {
|
|
253
|
+
results: page1Modules,
|
|
254
|
+
links: [{ rel: 'next', uri: '/ws/rest/v1/module?startIndex=50' }],
|
|
255
|
+
},
|
|
256
|
+
} as any)
|
|
257
|
+
.mockResolvedValueOnce({
|
|
258
|
+
data: {
|
|
259
|
+
results: page2Modules,
|
|
260
|
+
links: [],
|
|
261
|
+
},
|
|
262
|
+
} as any);
|
|
263
|
+
|
|
264
|
+
mockIsVersionSatisfied.mockReturnValue(true);
|
|
265
|
+
|
|
266
|
+
window.installedModules = [
|
|
267
|
+
['@openmrs/esm-test-app', { backendDependencies: { 'module-0': '1.0.0', 'module-50': '1.0.0' } }],
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
const result = await checkModules();
|
|
271
|
+
|
|
272
|
+
// Should find both modules across pages
|
|
273
|
+
expect(result[0].dependencies).toHaveLength(2);
|
|
274
|
+
expect(result[0].dependencies.every((d) => d.type === 'okay')).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should handle fetch errors gracefully by returning empty backend modules', async () => {
|
|
278
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
279
|
+
|
|
280
|
+
mockOpenmrsFetch.mockRejectedValue(new Error('Network error'));
|
|
281
|
+
|
|
282
|
+
window.installedModules = [['@openmrs/esm-test-app', { backendDependencies: { 'webservices.rest': '2.0.0' } }]];
|
|
283
|
+
|
|
284
|
+
const result = await checkModules();
|
|
285
|
+
|
|
286
|
+
// Error should be logged
|
|
287
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
288
|
+
|
|
289
|
+
// Should treat all dependencies as missing when fetch fails
|
|
290
|
+
expect(result).toHaveLength(1);
|
|
291
|
+
expect(result[0].dependencies).toHaveLength(1);
|
|
292
|
+
expect(result[0].dependencies[0].type).toBe('missing');
|
|
293
|
+
|
|
294
|
+
consoleErrorSpy.mockRestore();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should warn when reaching pagination limit', async () => {
|
|
298
|
+
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
299
|
+
|
|
300
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
301
|
+
data: {
|
|
302
|
+
results: [{ uuid: 'test-module', version: '1.0.0' }],
|
|
303
|
+
links: [{ rel: 'next', uri: 'module?startIndex=50' }],
|
|
304
|
+
},
|
|
305
|
+
} as any);
|
|
306
|
+
|
|
307
|
+
window.installedModules = [['@openmrs/esm-test-app', { backendDependencies: { 'test-module': '1.0.0' } }]];
|
|
308
|
+
|
|
309
|
+
await checkModules();
|
|
310
|
+
|
|
311
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Reached maximum page limit'));
|
|
312
|
+
|
|
313
|
+
consoleWarnSpy.mockRestore();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('hasInvalidDependencies', () => {
|
|
318
|
+
it('should return false when all dependencies are okay', () => {
|
|
319
|
+
const modules = [
|
|
320
|
+
{
|
|
321
|
+
name: '@openmrs/esm-test-app',
|
|
322
|
+
dependencies: [
|
|
323
|
+
{
|
|
324
|
+
name: 'webservices.rest',
|
|
325
|
+
requiredVersion: '^2.0.0',
|
|
326
|
+
installedVersion: '2.24.0',
|
|
327
|
+
type: 'okay' as ResolvedBackendModuleType,
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
expect(hasInvalidDependencies(modules)).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should return false when there are no dependencies', () => {
|
|
337
|
+
const modules = [
|
|
338
|
+
{
|
|
339
|
+
name: '@openmrs/esm-test-app',
|
|
340
|
+
dependencies: [],
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
expect(hasInvalidDependencies(modules)).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should return true when there are missing dependencies', () => {
|
|
348
|
+
const modules = [
|
|
349
|
+
{
|
|
350
|
+
name: '@openmrs/esm-test-app',
|
|
351
|
+
dependencies: [
|
|
352
|
+
{
|
|
353
|
+
name: 'missing-module',
|
|
354
|
+
requiredVersion: '1.0.0',
|
|
355
|
+
type: 'missing' as ResolvedBackendModuleType,
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
expect(hasInvalidDependencies(modules)).toBe(true);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should return true when there are version mismatches', () => {
|
|
365
|
+
const modules = [
|
|
366
|
+
{
|
|
367
|
+
name: '@openmrs/esm-test-app',
|
|
368
|
+
dependencies: [
|
|
369
|
+
{
|
|
370
|
+
name: 'webservices.rest',
|
|
371
|
+
requiredVersion: '^3.0.0',
|
|
372
|
+
installedVersion: '2.24.0',
|
|
373
|
+
type: 'version-mismatch' as ResolvedBackendModuleType,
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
expect(hasInvalidDependencies(modules)).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should return true if any module has invalid dependencies', () => {
|
|
383
|
+
const modules = [
|
|
384
|
+
{
|
|
385
|
+
name: '@openmrs/esm-app-1',
|
|
386
|
+
dependencies: [
|
|
387
|
+
{
|
|
388
|
+
name: 'module-1',
|
|
389
|
+
requiredVersion: '1.0.0',
|
|
390
|
+
installedVersion: '1.0.0',
|
|
391
|
+
type: 'okay' as ResolvedBackendModuleType,
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: '@openmrs/esm-app-2',
|
|
397
|
+
dependencies: [
|
|
398
|
+
{
|
|
399
|
+
name: 'module-2',
|
|
400
|
+
requiredVersion: '2.0.0',
|
|
401
|
+
type: 'missing' as ResolvedBackendModuleType,
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
expect(hasInvalidDependencies(modules)).toBe(true);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should return true if any dependency in any module is invalid', () => {
|
|
411
|
+
const modules = [
|
|
412
|
+
{
|
|
413
|
+
name: '@openmrs/esm-app',
|
|
414
|
+
dependencies: [
|
|
415
|
+
{
|
|
416
|
+
name: 'module-1',
|
|
417
|
+
requiredVersion: '1.0.0',
|
|
418
|
+
installedVersion: '1.0.0',
|
|
419
|
+
type: 'okay' as ResolvedBackendModuleType,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: 'module-2',
|
|
423
|
+
requiredVersion: '2.0.0',
|
|
424
|
+
installedVersion: '1.0.0',
|
|
425
|
+
type: 'version-mismatch' as ResolvedBackendModuleType,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
},
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
expect(hasInvalidDependencies(modules)).toBe(true);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
});
|
|
@@ -39,10 +39,12 @@ interface BackendModule {
|
|
|
39
39
|
|
|
40
40
|
let cachedFrontendModules: Array<ResolvedDependenciesModule>;
|
|
41
41
|
|
|
42
|
+
const MAX_PAGES = 50;
|
|
43
|
+
|
|
42
44
|
async function initInstalledBackendModules(): Promise<Array<BackendModule>> {
|
|
43
45
|
try {
|
|
44
|
-
const
|
|
45
|
-
return
|
|
46
|
+
const modules = await fetchInstalledBackendModules();
|
|
47
|
+
return modules;
|
|
46
48
|
} catch (err) {
|
|
47
49
|
console.error(err);
|
|
48
50
|
}
|
|
@@ -88,10 +90,49 @@ function checkIfModulesAreInstalled(
|
|
|
88
90
|
};
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
function fetchInstalledBackendModules() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
async function fetchInstalledBackendModules(): Promise<Array<BackendModule>> {
|
|
94
|
+
const collected: Array<BackendModule> = [];
|
|
95
|
+
let nextUrl: string | null = `${restBaseUrl}/module?v=custom:(uuid,version)`;
|
|
96
|
+
let safetyCounter = 0;
|
|
97
|
+
|
|
98
|
+
const resolveNext = (url?: string | null) => {
|
|
99
|
+
if (!url) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (/^https?:\/\//i.test(url)) {
|
|
103
|
+
return url;
|
|
104
|
+
}
|
|
105
|
+
if (url.startsWith('/')) {
|
|
106
|
+
return url;
|
|
107
|
+
}
|
|
108
|
+
return `${restBaseUrl}/${url.replace(/^\/?/, '')}`;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
while (nextUrl && safetyCounter < MAX_PAGES) {
|
|
112
|
+
try {
|
|
113
|
+
const { data } = await openmrsFetch(nextUrl, { method: 'GET' });
|
|
114
|
+
const pageResults: Array<BackendModule> = Array.isArray(data?.results) ? data.results : [];
|
|
115
|
+
|
|
116
|
+
collected.push(...pageResults);
|
|
117
|
+
|
|
118
|
+
const links: Array<{ rel?: string; uri?: string }> = Array.isArray(data?.links) ? data.links : [];
|
|
119
|
+
const nextLink = links.find((l) => (l.rel || '').toLowerCase() === 'next');
|
|
120
|
+
|
|
121
|
+
nextUrl = resolveNext(nextLink?.uri ?? null);
|
|
122
|
+
safetyCounter += 1;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error(`Failed to fetch backend modules on request ${safetyCounter + 1} (URL: ${nextUrl})`, e);
|
|
125
|
+
throw new Error(`Failed to fetch backend modules: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (nextUrl && safetyCounter >= MAX_PAGES) {
|
|
130
|
+
console.warn(
|
|
131
|
+
`Reached maximum page limit (${MAX_PAGES}) while fetching backend modules. There may be more data available at: ${nextUrl}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return collected;
|
|
95
136
|
}
|
|
96
137
|
|
|
97
138
|
function getMissingBackendModules(
|
|
@@ -129,11 +170,8 @@ function getInstalledAndRequiredBackendModules(
|
|
|
129
170
|
version: declaredBackendModules[key],
|
|
130
171
|
}));
|
|
131
172
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return requiredModule.uuid === installedModule.uuid;
|
|
135
|
-
});
|
|
136
|
-
});
|
|
173
|
+
const installedUuids = new Set(installedBackendModules.map((module) => module.uuid));
|
|
174
|
+
return requiredModules.filter((requiredModule) => installedUuids.has(requiredModule.uuid));
|
|
137
175
|
}
|
|
138
176
|
|
|
139
177
|
function getInstalledVersion(
|
|
@@ -141,7 +179,7 @@ function getInstalledVersion(
|
|
|
141
179
|
installedBackendModules: Array<BackendModule>,
|
|
142
180
|
) {
|
|
143
181
|
const moduleName = installedAndRequiredBackendModule.uuid;
|
|
144
|
-
return installedBackendModules.find((mod) => mod.uuid
|
|
182
|
+
return installedBackendModules.find((mod) => mod.uuid === moduleName)?.version ?? '';
|
|
145
183
|
}
|
|
146
184
|
|
|
147
185
|
function getResolvedModuleType(requiredVersion: string, installedVersion: string): ResolvedBackendModuleType {
|
|
@@ -176,3 +214,8 @@ export async function checkModules(): Promise<Array<ResolvedDependenciesModule>>
|
|
|
176
214
|
export function hasInvalidDependencies(frontendModules: Array<ResolvedDependenciesModule>) {
|
|
177
215
|
return frontendModules.some((m) => m.dependencies.some((n) => n.type !== 'okay'));
|
|
178
216
|
}
|
|
217
|
+
|
|
218
|
+
// For use in tests
|
|
219
|
+
export function clearCache() {
|
|
220
|
+
cachedFrontendModules = undefined as any;
|
|
221
|
+
}
|