@dhis2/app-service-data 3.4.3 → 3.4.4
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/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +3 -10
- package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +0 -17
- package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.js +23 -10
- package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +22 -1
- package/build/cjs/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.js +13 -0
- package/build/cjs/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.test.js +21 -0
- package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +1 -5
- package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +1 -18
- package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.js +19 -8
- package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +23 -2
- package/build/es/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.js +4 -0
- package/build/es/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.test.js +18 -0
- package/build/types/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.d.ts +0 -1
- package/build/types/links/RestAPILink/queryToRequestOptions/requestContentType.d.ts +3 -3
- package/build/types/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.d.ts +2 -0
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.isAppInstall = exports.isStaticContentUpload = exports.isMessageConversationAttachment = exports.isFileResourceUpload = exports.isDataValue = void 0;
|
|
7
7
|
|
|
8
8
|
/*
|
|
9
9
|
* Requests that expect a "multipart/form-data" Content-Type have been collected by scanning
|
|
@@ -45,13 +45,6 @@ exports.isStaticContentUpload = isStaticContentUpload;
|
|
|
45
45
|
|
|
46
46
|
const isAppInstall = (type, {
|
|
47
47
|
resource
|
|
48
|
-
}) => type === 'create' && resource === 'apps';
|
|
48
|
+
}) => type === 'create' && resource === 'apps';
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
exports.isAppInstall = isAppInstall;
|
|
52
|
-
|
|
53
|
-
const isSvgConversion = (type, {
|
|
54
|
-
resource
|
|
55
|
-
}) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
|
|
56
|
-
|
|
57
|
-
exports.isSvgConversion = isSvgConversion;
|
|
50
|
+
exports.isAppInstall = isAppInstall;
|
|
@@ -71,21 +71,4 @@ describe('isAppInstall', () => {
|
|
|
71
71
|
resource: 'notApps'
|
|
72
72
|
})).toEqual(false);
|
|
73
73
|
});
|
|
74
|
-
});
|
|
75
|
-
describe('isSvgConversion', () => {
|
|
76
|
-
it('returns true for a POST to "svg.png"', () => {
|
|
77
|
-
expect((0, _multipartFormDataMatchers.isSvgConversion)('create', {
|
|
78
|
-
resource: 'svg.png'
|
|
79
|
-
})).toEqual(true);
|
|
80
|
-
});
|
|
81
|
-
it('returns true for a POST to "svg.pdf"', () => {
|
|
82
|
-
expect((0, _multipartFormDataMatchers.isSvgConversion)('create', {
|
|
83
|
-
resource: 'svg.pdf'
|
|
84
|
-
})).toEqual(true);
|
|
85
|
-
});
|
|
86
|
-
it('retuns false for a POST to a different resource', () => {
|
|
87
|
-
expect((0, _multipartFormDataMatchers.isSvgConversion)('create', {
|
|
88
|
-
resource: 'notSvg'
|
|
89
|
-
})).toEqual(false);
|
|
90
|
-
});
|
|
91
74
|
});
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.requestBodyForContentType = exports.requestHeadersForContentType = exports.requestContentType = exports.
|
|
6
|
+
exports.requestBodyForContentType = exports.requestHeadersForContentType = exports.requestContentType = exports.getConversionErrorMessage = void 0;
|
|
7
7
|
|
|
8
8
|
var multipartFormDataMatchers = _interopRequireWildcard(require("./multipartFormDataMatchers"));
|
|
9
9
|
|
|
10
10
|
var textPlainMatchers = _interopRequireWildcard(require("./textPlainMatchers"));
|
|
11
11
|
|
|
12
|
+
var xWwwFormUrlencodedMatchers = _interopRequireWildcard(require("./xWwwFormUrlencodedMatchers"));
|
|
13
|
+
|
|
12
14
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
13
15
|
|
|
14
16
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
@@ -17,20 +19,23 @@ const resourceExpectsTextPlain = (type, query) => Object.values(textPlainMatcher
|
|
|
17
19
|
|
|
18
20
|
const resourceExpectsMultipartFormData = (type, query) => Object.values(multipartFormDataMatchers).some(multipartFormDataMatcher => multipartFormDataMatcher(type, query));
|
|
19
21
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
+
const resourceExpectsXWwwFormUrlencoded = (type, query) => Object.values(xWwwFormUrlencodedMatchers).some(xWwwFormUrlencodedMatcher => xWwwFormUrlencodedMatcher(type, query));
|
|
23
|
+
|
|
24
|
+
const getConversionErrorMessage = outputType => "Could not convert data to ".concat(outputType, ": object does not have own enumerable string-keyed properties");
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
exports.getConversionErrorMessage = getConversionErrorMessage;
|
|
27
|
+
|
|
28
|
+
const convertData = (data, initialValue) => {
|
|
24
29
|
const dataEntries = Object.entries(data);
|
|
25
30
|
|
|
26
31
|
if (dataEntries.length === 0) {
|
|
27
|
-
throw new Error(
|
|
32
|
+
throw new Error(getConversionErrorMessage(initialValue.constructor.name));
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
return dataEntries.reduce((
|
|
31
|
-
|
|
32
|
-
return
|
|
33
|
-
},
|
|
35
|
+
return dataEntries.reduce((convertedData, [key, value]) => {
|
|
36
|
+
convertedData.append(key, value);
|
|
37
|
+
return convertedData;
|
|
38
|
+
}, initialValue);
|
|
34
39
|
};
|
|
35
40
|
|
|
36
41
|
const requestContentType = (type, query) => {
|
|
@@ -50,6 +55,10 @@ const requestContentType = (type, query) => {
|
|
|
50
55
|
return 'multipart/form-data';
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
if (resourceExpectsXWwwFormUrlencoded(type, query)) {
|
|
59
|
+
return 'application/x-www-form-urlencoded';
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
return 'application/json';
|
|
54
63
|
};
|
|
55
64
|
|
|
@@ -86,7 +95,11 @@ const requestBodyForContentType = (contentType, {
|
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
if (contentType === 'multipart/form-data') {
|
|
89
|
-
return
|
|
98
|
+
return convertData(data, new FormData());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
|
102
|
+
return convertData(data, new URLSearchParams());
|
|
90
103
|
} // 'text/plain'
|
|
91
104
|
|
|
92
105
|
|
|
@@ -88,7 +88,28 @@ describe('requestBodyForContentType', () => {
|
|
|
88
88
|
type: 'text/plain'
|
|
89
89
|
})
|
|
90
90
|
});
|
|
91
|
-
}).toThrow(new Error(
|
|
91
|
+
}).toThrow(new Error('Could not convert data to FormData: object does not have own enumerable string-keyed properties'));
|
|
92
|
+
});
|
|
93
|
+
it('converts to URLSearchParams if contentType is "application/x-www-form-urlencoded"', () => {
|
|
94
|
+
const data = {
|
|
95
|
+
a: 'AAA'
|
|
96
|
+
};
|
|
97
|
+
const result = (0, _requestContentType.requestBodyForContentType)('application/x-www-form-urlencoded', {
|
|
98
|
+
resource: 'test',
|
|
99
|
+
data
|
|
100
|
+
});
|
|
101
|
+
expect(result instanceof URLSearchParams).toEqual(true);
|
|
102
|
+
expect(result.get('a')).toEqual('AAA');
|
|
103
|
+
});
|
|
104
|
+
it('throws an error if contentType is "application/x-www-form-urlencoded" and data does have own string-keyd properties', () => {
|
|
105
|
+
expect(() => {
|
|
106
|
+
(0, _requestContentType.requestBodyForContentType)('application/x-www-form-urlencoded', {
|
|
107
|
+
resource: 'test',
|
|
108
|
+
data: new File(['foo'], 'foo.txt', {
|
|
109
|
+
type: 'text/plain'
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
}).toThrow(new Error('Could not convert data to URLSearchParams: object does not have own enumerable string-keyed properties'));
|
|
92
113
|
});
|
|
93
114
|
it('returns the data as received if contentType is "text/plain"', () => {
|
|
94
115
|
const data = 'Something';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isSvgConversion = void 0;
|
|
7
|
+
|
|
8
|
+
// POST to convert an SVG file
|
|
9
|
+
const isSvgConversion = (type, {
|
|
10
|
+
resource
|
|
11
|
+
}) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
|
|
12
|
+
|
|
13
|
+
exports.isSvgConversion = isSvgConversion;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _xWwwFormUrlencodedMatchers = require("./xWwwFormUrlencodedMatchers");
|
|
4
|
+
|
|
5
|
+
describe('isSvgConversion', () => {
|
|
6
|
+
it('returns true for a POST to "svg.png"', () => {
|
|
7
|
+
expect((0, _xWwwFormUrlencodedMatchers.isSvgConversion)('create', {
|
|
8
|
+
resource: 'svg.png'
|
|
9
|
+
})).toEqual(true);
|
|
10
|
+
});
|
|
11
|
+
it('returns true for a POST to "svg.pdf"', () => {
|
|
12
|
+
expect((0, _xWwwFormUrlencodedMatchers.isSvgConversion)('create', {
|
|
13
|
+
resource: 'svg.pdf'
|
|
14
|
+
})).toEqual(true);
|
|
15
|
+
});
|
|
16
|
+
it('retuns false for a POST to a different resource', () => {
|
|
17
|
+
expect((0, _xWwwFormUrlencodedMatchers.isSvgConversion)('create', {
|
|
18
|
+
resource: 'notSvg'
|
|
19
|
+
})).toEqual(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -26,8 +26,4 @@ export const isStaticContentUpload = (type, {
|
|
|
26
26
|
|
|
27
27
|
export const isAppInstall = (type, {
|
|
28
28
|
resource
|
|
29
|
-
}) => type === 'create' && resource === 'apps';
|
|
30
|
-
|
|
31
|
-
export const isSvgConversion = (type, {
|
|
32
|
-
resource
|
|
33
|
-
}) => type === 'create' && (resource === 'svg.png' || resource === 'svg.pdf');
|
|
29
|
+
}) => type === 'create' && resource === 'apps';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall,
|
|
1
|
+
import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall, isDataValue } from './multipartFormDataMatchers';
|
|
2
2
|
describe('isDataValue', () => {
|
|
3
3
|
it('returns true for a POST to "dataValues"', () => {
|
|
4
4
|
expect(isDataValue('create', {
|
|
@@ -68,21 +68,4 @@ describe('isAppInstall', () => {
|
|
|
68
68
|
resource: 'notApps'
|
|
69
69
|
})).toEqual(false);
|
|
70
70
|
});
|
|
71
|
-
});
|
|
72
|
-
describe('isSvgConversion', () => {
|
|
73
|
-
it('returns true for a POST to "svg.png"', () => {
|
|
74
|
-
expect(isSvgConversion('create', {
|
|
75
|
-
resource: 'svg.png'
|
|
76
|
-
})).toEqual(true);
|
|
77
|
-
});
|
|
78
|
-
it('returns true for a POST to "svg.pdf"', () => {
|
|
79
|
-
expect(isSvgConversion('create', {
|
|
80
|
-
resource: 'svg.pdf'
|
|
81
|
-
})).toEqual(true);
|
|
82
|
-
});
|
|
83
|
-
it('retuns false for a POST to a different resource', () => {
|
|
84
|
-
expect(isSvgConversion('create', {
|
|
85
|
-
resource: 'notSvg'
|
|
86
|
-
})).toEqual(false);
|
|
87
|
-
});
|
|
88
71
|
});
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import * as multipartFormDataMatchers from './multipartFormDataMatchers';
|
|
2
2
|
import * as textPlainMatchers from './textPlainMatchers';
|
|
3
|
+
import * as xWwwFormUrlencodedMatchers from './xWwwFormUrlencodedMatchers';
|
|
3
4
|
|
|
4
5
|
const resourceExpectsTextPlain = (type, query) => Object.values(textPlainMatchers).some(textPlainMatcher => textPlainMatcher(type, query));
|
|
5
6
|
|
|
6
7
|
const resourceExpectsMultipartFormData = (type, query) => Object.values(multipartFormDataMatchers).some(multipartFormDataMatcher => multipartFormDataMatcher(type, query));
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
const resourceExpectsXWwwFormUrlencoded = (type, query) => Object.values(xWwwFormUrlencodedMatchers).some(xWwwFormUrlencodedMatcher => xWwwFormUrlencodedMatcher(type, query));
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
+
export const getConversionErrorMessage = outputType => "Could not convert data to ".concat(outputType, ": object does not have own enumerable string-keyed properties");
|
|
12
|
+
|
|
13
|
+
const convertData = (data, initialValue) => {
|
|
11
14
|
const dataEntries = Object.entries(data);
|
|
12
15
|
|
|
13
16
|
if (dataEntries.length === 0) {
|
|
14
|
-
throw new Error(
|
|
17
|
+
throw new Error(getConversionErrorMessage(initialValue.constructor.name));
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
return dataEntries.reduce((
|
|
18
|
-
|
|
19
|
-
return
|
|
20
|
-
},
|
|
20
|
+
return dataEntries.reduce((convertedData, [key, value]) => {
|
|
21
|
+
convertedData.append(key, value);
|
|
22
|
+
return convertedData;
|
|
23
|
+
}, initialValue);
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
export const requestContentType = (type, query) => {
|
|
@@ -37,6 +40,10 @@ export const requestContentType = (type, query) => {
|
|
|
37
40
|
return 'multipart/form-data';
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
if (resourceExpectsXWwwFormUrlencoded(type, query)) {
|
|
44
|
+
return 'application/x-www-form-urlencoded';
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
return 'application/json';
|
|
41
48
|
};
|
|
42
49
|
export const requestHeadersForContentType = contentType => {
|
|
@@ -67,7 +74,11 @@ export const requestBodyForContentType = (contentType, {
|
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
if (contentType === 'multipart/form-data') {
|
|
70
|
-
return
|
|
77
|
+
return convertData(data, new FormData());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
|
81
|
+
return convertData(data, new URLSearchParams());
|
|
71
82
|
} // 'text/plain'
|
|
72
83
|
|
|
73
84
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { requestContentType, requestHeadersForContentType, requestBodyForContentType
|
|
1
|
+
import { requestContentType, requestHeadersForContentType, requestBodyForContentType } from './requestContentType';
|
|
2
2
|
describe('requestContentType', () => {
|
|
3
3
|
it('returns "application/json" for a normal resource', () => {
|
|
4
4
|
expect(requestContentType('create', {
|
|
@@ -85,7 +85,28 @@ describe('requestBodyForContentType', () => {
|
|
|
85
85
|
type: 'text/plain'
|
|
86
86
|
})
|
|
87
87
|
});
|
|
88
|
-
}).toThrow(new Error(
|
|
88
|
+
}).toThrow(new Error('Could not convert data to FormData: object does not have own enumerable string-keyed properties'));
|
|
89
|
+
});
|
|
90
|
+
it('converts to URLSearchParams if contentType is "application/x-www-form-urlencoded"', () => {
|
|
91
|
+
const data = {
|
|
92
|
+
a: 'AAA'
|
|
93
|
+
};
|
|
94
|
+
const result = requestBodyForContentType('application/x-www-form-urlencoded', {
|
|
95
|
+
resource: 'test',
|
|
96
|
+
data
|
|
97
|
+
});
|
|
98
|
+
expect(result instanceof URLSearchParams).toEqual(true);
|
|
99
|
+
expect(result.get('a')).toEqual('AAA');
|
|
100
|
+
});
|
|
101
|
+
it('throws an error if contentType is "application/x-www-form-urlencoded" and data does have own string-keyd properties', () => {
|
|
102
|
+
expect(() => {
|
|
103
|
+
requestBodyForContentType('application/x-www-form-urlencoded', {
|
|
104
|
+
resource: 'test',
|
|
105
|
+
data: new File(['foo'], 'foo.txt', {
|
|
106
|
+
type: 'text/plain'
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
}).toThrow(new Error('Could not convert data to URLSearchParams: object does not have own enumerable string-keyed properties'));
|
|
89
110
|
});
|
|
90
111
|
it('returns the data as received if contentType is "text/plain"', () => {
|
|
91
112
|
const data = 'Something';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isSvgConversion } from './xWwwFormUrlencodedMatchers';
|
|
2
|
+
describe('isSvgConversion', () => {
|
|
3
|
+
it('returns true for a POST to "svg.png"', () => {
|
|
4
|
+
expect(isSvgConversion('create', {
|
|
5
|
+
resource: 'svg.png'
|
|
6
|
+
})).toEqual(true);
|
|
7
|
+
});
|
|
8
|
+
it('returns true for a POST to "svg.pdf"', () => {
|
|
9
|
+
expect(isSvgConversion('create', {
|
|
10
|
+
resource: 'svg.pdf'
|
|
11
|
+
})).toEqual(true);
|
|
12
|
+
});
|
|
13
|
+
it('retuns false for a POST to a different resource', () => {
|
|
14
|
+
expect(isSvgConversion('create', {
|
|
15
|
+
resource: 'notSvg'
|
|
16
|
+
})).toEqual(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -4,4 +4,3 @@ export declare const isFileResourceUpload: (type: FetchType, { resource }: Resol
|
|
|
4
4
|
export declare const isMessageConversationAttachment: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
|
|
5
5
|
export declare const isStaticContentUpload: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
|
|
6
6
|
export declare const isAppInstall: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
|
|
7
|
-
export declare const isSvgConversion: (type: FetchType, { resource }: ResolvedResourceQuery) => boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ResolvedResourceQuery, FetchType } from '../../../engine';
|
|
2
|
-
declare type RequestContentType = 'application/json' | 'application/json-patch+json' | 'text/plain' | 'multipart/form-data' | null;
|
|
3
|
-
export declare const
|
|
2
|
+
declare type RequestContentType = 'application/json' | 'application/json-patch+json' | 'text/plain' | 'multipart/form-data' | 'application/x-www-form-urlencoded' | null;
|
|
3
|
+
export declare const getConversionErrorMessage: (outputType: string) => string;
|
|
4
4
|
export declare const requestContentType: (type: FetchType, query: ResolvedResourceQuery) => null | RequestContentType;
|
|
5
5
|
export declare const requestHeadersForContentType: (contentType: RequestContentType) => undefined | Record<'Content-Type', string>;
|
|
6
|
-
export declare const requestBodyForContentType: (contentType: RequestContentType, { data }: ResolvedResourceQuery) => undefined | string | FormData;
|
|
6
|
+
export declare const requestBodyForContentType: (contentType: RequestContentType, { data }: ResolvedResourceQuery) => undefined | string | FormData | URLSearchParams;
|
|
7
7
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2/app-service-data",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"main": "./build/cjs/index.js",
|
|
5
5
|
"module": "./build/es/index.js",
|
|
6
6
|
"types": "build/types/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build/**"
|
|
23
23
|
],
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@dhis2/app-service-config": "3.4.
|
|
25
|
+
"@dhis2/app-service-config": "3.4.4",
|
|
26
26
|
"@dhis2/cli-app-scripts": "^7.1.1",
|
|
27
27
|
"prop-types": "^15.7.2",
|
|
28
28
|
"react": "^16.8",
|