codeplay-common 3.0.7 → 3.0.8
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/.gitattributes +2 -2
- package/LICENSE +21 -21
- package/README.md +11 -11
- package/files/buildCodeplay/{codeplayBeforeBuild-5.4.js → codeplayBeforeBuild-6.0.js} +206 -20
- package/files/buildCodeplay/versions.json +21 -0
- package/files/finalrelease +924 -924
- package/files/iap-install-2.js +145 -145
- package/files/ionic.config.json +6 -6
- package/package.json +16 -16
- package/scripts/sync-files.js +86 -86
- package/scripts/uninstall.js +77 -77
package/.gitattributes
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
# Auto detect text files and perform LF normalization
|
|
2
|
-
* text=auto
|
|
1
|
+
# Auto detect text files and perform LF normalization
|
|
2
|
+
* text=auto
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Merbin Joe
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Merbin Joe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# codeplay-common
|
|
2
|
-
Everything automatted in Capacitor apps, easy to maintain many apps in capacitor. It will reduce your 40% of work, with common file code.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Easy add splashscreen and set animation
|
|
6
|
-
Admob id automatically from capacitor.config.json file
|
|
7
|
-
Make three build file for different store as per our requirement
|
|
8
|
-
Based on store install various IAP plugins
|
|
9
|
-
Make it to ionic project
|
|
10
|
-
|
|
11
|
-
Donate to get full code including many common functions
|
|
1
|
+
# codeplay-common
|
|
2
|
+
Everything automatted in Capacitor apps, easy to maintain many apps in capacitor. It will reduce your 40% of work, with common file code.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Easy add splashscreen and set animation
|
|
6
|
+
Admob id automatically from capacitor.config.json file
|
|
7
|
+
Make three build file for different store as per our requirement
|
|
8
|
+
Based on store install various IAP plugins
|
|
9
|
+
Make it to ionic project
|
|
10
|
+
|
|
11
|
+
Donate to get full code including many common functions
|
|
12
12
|
https://ko-fi.com/codeplay
|
|
@@ -2,18 +2,26 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const plist = require('plist');
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
5
12
|
const { readFileSync } = require("fs");
|
|
6
13
|
|
|
7
14
|
const ENABLE_AUTO_UPDATE = true;
|
|
15
|
+
const USE_LIVE_SERVER_VERSION = true;
|
|
8
16
|
|
|
9
17
|
const configPath = path.join(process.cwd(), 'capacitor.config.json');
|
|
10
18
|
|
|
11
19
|
// Expected plugin list with minimum versions
|
|
12
|
-
const requiredPlugins = [
|
|
20
|
+
/* const requiredPlugins = [
|
|
13
21
|
|
|
14
22
|
{ pattern: /backbutton-(\d+\.\d+)\.js$/, minVersion: '2.0', required: true, baseDir: 'js',mandatoryUpdate: true},
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
|
|
17
25
|
{ pattern: /common-(\d+\.\d+)(?:-beta-(\d+))?\.js$/, minVersion: '6.0', required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
18
26
|
|
|
19
27
|
{ pattern: /localization_settings-(\d+\.\d+)\.js$/, minVersion: '1.1', required: true, baseDir: 'js',mandatoryUpdate: false },
|
|
@@ -39,7 +47,72 @@ const requiredPlugins = [
|
|
|
39
47
|
|
|
40
48
|
{ pattern: /certificatejs-(\d+\.\d+)$/, minVersion: '1.6', isFolder: true , required: true, baseDir: 'certificate',mandatoryUpdate: true }
|
|
41
49
|
|
|
42
|
-
];
|
|
50
|
+
]; */
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
function requireOrInstall(packageName) {
|
|
54
|
+
try {
|
|
55
|
+
return require(packageName);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
|
|
58
|
+
console.log(`📦 "${packageName}" not found. Installing automatically...`);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
execSync(`npm install ${packageName}`, { stdio: "inherit" });
|
|
62
|
+
console.log(`✅ "${packageName}" installed successfully.`);
|
|
63
|
+
} catch (installErr) {
|
|
64
|
+
console.error(`❌ Failed to install "${packageName}".`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Try loading again
|
|
69
|
+
return require(packageName);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const AdmZip = requireOrInstall("adm-zip");
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
const versionsFile = path.join(__dirname, "versions.json");
|
|
79
|
+
|
|
80
|
+
function loadRequiredPlugins() {
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(versionsFile)) {
|
|
83
|
+
console.error("❌ versions.json not found");
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const json = JSON.parse(fs.readFileSync(versionsFile, "utf8"));
|
|
88
|
+
|
|
89
|
+
return json.plugins.map(p => ({
|
|
90
|
+
...p,
|
|
91
|
+
pattern: new RegExp(p.pattern)
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let requiredPlugins = loadRequiredPlugins();
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async function downloadAndExtractZip(url, destFolder) {
|
|
104
|
+
|
|
105
|
+
const zipPath = destFolder + ".zip";
|
|
106
|
+
|
|
107
|
+
await downloadFile(url, zipPath);
|
|
108
|
+
|
|
109
|
+
const zip = new AdmZip(zipPath);
|
|
110
|
+
zip.extractAllTo(destFolder, true);
|
|
111
|
+
|
|
112
|
+
fs.unlinkSync(zipPath);
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
43
116
|
|
|
44
117
|
|
|
45
118
|
|
|
@@ -47,7 +120,8 @@ const requiredPlugins = [
|
|
|
47
120
|
|
|
48
121
|
|
|
49
122
|
//Check codeplay-common latest version installed or not Start
|
|
50
|
-
const { execSync } = require('child_process');
|
|
123
|
+
//const { execSync } = require('child_process');
|
|
124
|
+
|
|
51
125
|
|
|
52
126
|
function getInstalledVersion(packageName) {
|
|
53
127
|
try {
|
|
@@ -1124,33 +1198,50 @@ function updateImports(oldName, newName) {
|
|
|
1124
1198
|
*/
|
|
1125
1199
|
|
|
1126
1200
|
|
|
1201
|
+
let _serverVersions = null;
|
|
1202
|
+
async function fetchVersions() {
|
|
1203
|
+
|
|
1204
|
+
if (_serverVersions) return _serverVersions;
|
|
1205
|
+
|
|
1206
|
+
return new Promise((resolve) => {
|
|
1127
1207
|
|
|
1128
|
-
function fetchVersions() {
|
|
1129
|
-
return new Promise((resolve, reject) => {
|
|
1130
1208
|
https.get(
|
|
1131
1209
|
"https://htmlcodeplay.com/code-play-plugin/versions.json",
|
|
1210
|
+
{ timeout: 5000 },
|
|
1132
1211
|
res => {
|
|
1212
|
+
|
|
1133
1213
|
let data = "";
|
|
1134
1214
|
|
|
1135
1215
|
res.on("data", chunk => data += chunk);
|
|
1216
|
+
|
|
1136
1217
|
res.on("end", () => {
|
|
1218
|
+
|
|
1137
1219
|
try {
|
|
1138
|
-
|
|
1220
|
+
|
|
1221
|
+
_serverVersions = JSON.parse(data);
|
|
1222
|
+
|
|
1223
|
+
resolve(_serverVersions);
|
|
1224
|
+
|
|
1139
1225
|
} catch {
|
|
1226
|
+
|
|
1140
1227
|
resolve(null);
|
|
1228
|
+
|
|
1141
1229
|
}
|
|
1230
|
+
|
|
1142
1231
|
});
|
|
1232
|
+
|
|
1143
1233
|
}
|
|
1234
|
+
|
|
1144
1235
|
).on("error", () => resolve(null));
|
|
1236
|
+
|
|
1145
1237
|
});
|
|
1238
|
+
|
|
1146
1239
|
}
|
|
1147
1240
|
|
|
1148
1241
|
|
|
1149
1242
|
|
|
1150
1243
|
async function autoUpdatePlugin(pluginDef, pluginInfo) {
|
|
1151
1244
|
|
|
1152
|
-
if (pluginDef.isFolder) return false;
|
|
1153
|
-
|
|
1154
1245
|
const versions = await fetchVersions();
|
|
1155
1246
|
|
|
1156
1247
|
if (!versions) {
|
|
@@ -1171,24 +1262,88 @@ async function autoUpdatePlugin(pluginDef, pluginInfo) {
|
|
|
1171
1262
|
return false;
|
|
1172
1263
|
}
|
|
1173
1264
|
|
|
1174
|
-
|
|
1265
|
+
// ===============================
|
|
1266
|
+
// FOLDER PLUGIN UPDATE
|
|
1267
|
+
// ===============================
|
|
1268
|
+
if (pluginDef.isFolder) {
|
|
1269
|
+
|
|
1270
|
+
const zipName = `${baseName}-${latestVersion}.zip`;
|
|
1271
|
+
const url = `https://htmlcodeplay.com/code-play-plugin/${zipName}`;
|
|
1272
|
+
|
|
1273
|
+
const destRoot = path.join(srcDir, pluginDef.destDir || pluginDef.baseDir || '');
|
|
1274
|
+
const oldPath = path.join(destRoot, pluginInfo.name);
|
|
1275
|
+
const newPath = path.join(destRoot, `${baseName}-${latestVersion}`);
|
|
1276
|
+
|
|
1277
|
+
if (!(await urlExists(url))) return false;
|
|
1278
|
+
|
|
1279
|
+
fs.rmSync(oldPath, { recursive: true, force: true });
|
|
1280
|
+
|
|
1281
|
+
await downloadAndExtractZip(url, newPath);
|
|
1282
|
+
|
|
1283
|
+
console.log(`✅ Folder updated → ${baseName}-${latestVersion}`);
|
|
1175
1284
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// ===============================
|
|
1289
|
+
// FILE PLUGIN UPDATE
|
|
1290
|
+
// ===============================
|
|
1291
|
+
|
|
1292
|
+
const pluginDir = path.dirname(oldFullPath);
|
|
1293
|
+
|
|
1294
|
+
const variants = [
|
|
1295
|
+
`${baseName}-${latestVersion}.js`,
|
|
1296
|
+
`${baseName}-${latestVersion}-ios.js`
|
|
1297
|
+
];
|
|
1298
|
+
|
|
1299
|
+
let downloaded = [];
|
|
1300
|
+
|
|
1301
|
+
// Download new files first
|
|
1302
|
+
for (const fileName of variants) {
|
|
1178
1303
|
|
|
1179
|
-
|
|
1304
|
+
const url = `https://htmlcodeplay.com/code-play-plugin/${fileName}`;
|
|
1305
|
+
|
|
1306
|
+
console.log(`🔍 Checking latest: ${fileName}`);
|
|
1307
|
+
|
|
1308
|
+
if (await urlExists(url)) {
|
|
1309
|
+
|
|
1310
|
+
const destPath = path.join(pluginDir, fileName);
|
|
1311
|
+
|
|
1312
|
+
await downloadFile(url, destPath);
|
|
1313
|
+
|
|
1314
|
+
downloaded.push(fileName);
|
|
1315
|
+
|
|
1316
|
+
console.log(`⬇ Downloaded → ${fileName}`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1180
1319
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1320
|
+
// If nothing downloaded → skip
|
|
1321
|
+
if (downloaded.length === 0) {
|
|
1322
|
+
console.log(`❌ No files downloaded for ${baseName}`);
|
|
1183
1323
|
return false;
|
|
1184
1324
|
}
|
|
1185
1325
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1326
|
+
// Remove old versions AFTER download
|
|
1327
|
+
const existingFiles = fs.readdirSync(pluginDir);
|
|
1328
|
+
|
|
1329
|
+
existingFiles.forEach(file => {
|
|
1330
|
+
|
|
1331
|
+
if (
|
|
1332
|
+
file.startsWith(baseName + "-") &&
|
|
1333
|
+
file.endsWith(".js") &&
|
|
1334
|
+
!downloaded.includes(file)
|
|
1335
|
+
) {
|
|
1336
|
+
|
|
1337
|
+
const oldPath = path.join(pluginDir, file);
|
|
1338
|
+
|
|
1339
|
+
fs.unlinkSync(oldPath);
|
|
1340
|
+
|
|
1341
|
+
console.log(`🗑 Removed old file → ${file}`);
|
|
1342
|
+
}
|
|
1188
1343
|
|
|
1189
|
-
|
|
1344
|
+
});
|
|
1190
1345
|
|
|
1191
|
-
|
|
1346
|
+
const newFileName = `${baseName}-${latestVersion}.js`;
|
|
1192
1347
|
|
|
1193
1348
|
updateImports(oldFileName, newFileName);
|
|
1194
1349
|
|
|
@@ -1219,7 +1374,35 @@ async function autoUpdatePlugin(pluginDef, pluginInfo) {
|
|
|
1219
1374
|
|
|
1220
1375
|
|
|
1221
1376
|
|
|
1377
|
+
async function loadPluginVersions() {
|
|
1378
|
+
|
|
1379
|
+
if (!USE_LIVE_SERVER_VERSION) {
|
|
1380
|
+
console.log("ℹ️ Using local plugin versions (offline mode).");
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
console.log("🌐 Fetching plugin versions from server...");
|
|
1385
|
+
|
|
1386
|
+
const versions = await fetchVersions();
|
|
1387
|
+
|
|
1388
|
+
if (!versions || typeof versions !== "object") {
|
|
1389
|
+
console.log("⚠️ Server unavailable or invalid versions.json. Falling back to local versions.");
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
requiredPlugins.forEach(plugin => {
|
|
1394
|
+
|
|
1395
|
+
if (!plugin.name) return;
|
|
1396
|
+
|
|
1397
|
+
if (versions[plugin.name]) {
|
|
1398
|
+
plugin.minVersion = versions[plugin.name];
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
});
|
|
1222
1402
|
|
|
1403
|
+
console.log("✅ Plugin versions loaded from server.");
|
|
1404
|
+
|
|
1405
|
+
}
|
|
1223
1406
|
|
|
1224
1407
|
|
|
1225
1408
|
|
|
@@ -1303,7 +1486,7 @@ function checkPlugins() {
|
|
|
1303
1486
|
outdatedPlugins.forEach( p => {
|
|
1304
1487
|
const tag = p.mandatoryUpdate ? '🔥 MANDATORY' : '';
|
|
1305
1488
|
console.log(
|
|
1306
|
-
`
|
|
1489
|
+
` ⚠️ - ${p.name} (Current: ${p.currentVersion}, Required: ${p.requiredVersion}) ${tag}`
|
|
1307
1490
|
);
|
|
1308
1491
|
});
|
|
1309
1492
|
|
|
@@ -1653,6 +1836,9 @@ ensureGitignoreEntry('buildCodeplay/');
|
|
|
1653
1836
|
|
|
1654
1837
|
// Run the validation
|
|
1655
1838
|
(async () => {
|
|
1839
|
+
|
|
1840
|
+
await loadPluginVersions(); // 🔥 NEW
|
|
1841
|
+
|
|
1656
1842
|
await checkPlugins();
|
|
1657
1843
|
checkAndupdateDropInViteConfig();
|
|
1658
1844
|
checkAdmobConfigurationProperty()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins":[
|
|
3
|
+
{"name":"backbutton","pattern":"backbutton-(\\d+\\.\\d+)\\.js$","minVersion":"2.0","baseDir":"js","mandatoryUpdate":true},
|
|
4
|
+
{"name":"common","pattern":"common-(\\d+\\.\\d+)(?:-beta-(\\d+))?\\.js$","minVersion":"6.0","baseDir":"js","mandatoryUpdate":true},
|
|
5
|
+
{"name":"localization_settings","pattern":"localization_settings-(\\d+\\.\\d+)\\.js$","minVersion":"1.1","baseDir":"js","mandatoryUpdate":true},
|
|
6
|
+
{"name":"localization","pattern":"localization-(\\d+\\.\\d+)\\.js$","minVersion":"1.5","baseDir":"js","mandatoryUpdate":true},
|
|
7
|
+
{"name":"admob-emi","pattern":"Ads[\\\\/]admob-emi-(\\d+\\.\\d+)\\.js$","minVersion":"3.7","baseDir":"js","mandatoryUpdate":true},
|
|
8
|
+
{"name":"video-player","pattern":"video-player-(\\d+\\.\\d+)\\.js$","minVersion":"1.6","baseDir":"js","mandatoryUpdate":true},
|
|
9
|
+
{"name":"image-cropper","pattern":"image-cropper-(\\d+\\.\\d+)\\.js$","minVersion":"1.1","baseDir":"js","mandatoryUpdate":true},
|
|
10
|
+
{"name":"editor","pattern":"editor-(\\d+\\.\\d+)$","minVersion":"1.9","baseDir":"js","mandatoryUpdate":true,"isFolder":true},
|
|
11
|
+
{"name":"ffmpeg","pattern":"ffmpeg-(\\d+\\.\\d+)$","minVersion":"1.6","baseDir":"js","mandatoryUpdate":true,"isFolder":true},
|
|
12
|
+
{"name":"theme","pattern":"theme-(\\d+\\.\\d+)$","minVersion":"3.3","baseDir":"theme","destDir":"theme","mandatoryUpdate":true,"isFolder":true},
|
|
13
|
+
{"name":"certificatejs","pattern":"certificatejs-(\\d+\\.\\d+)$","minVersion":"1.6","baseDir":"certificate","destDir":"certificate","mandatoryUpdate":true,"isFolder":true},
|
|
14
|
+
{"name":"IAP","pattern":"IAP-(\\d+\\.\\d+)$","minVersion":"2.8","baseDir":"js/Ads","mandatoryUpdate":true,"isFolder":true},
|
|
15
|
+
{"name":"common-css","pattern":"common-(\\d+\\.\\d+)\\.less$","minVersion":"1.6","baseDir":"assets/css","mandatoryUpdate":true},
|
|
16
|
+
{"name":"localNotification_AppSettings","pattern":"localNotification_AppSettings-(\\d+\\.\\d+)\\.js$","minVersion":"1.0","baseDir":"js","mandatoryUpdate":true},
|
|
17
|
+
{"name":"localNotification","pattern":"localNotification-(\\d+\\.\\d+)\\.js$","minVersion":"2.2","baseDir":"js","mandatoryUpdate":true},
|
|
18
|
+
{"name":"onesignal","pattern":"onesignal-(\\d+\\.\\d+)\\.js$","minVersion":"2.3","baseDir":"js","mandatoryUpdate":true},
|
|
19
|
+
{"name":"saveToGalleryAndSaveAnyFile","pattern":"saveToGalleryAndSaveAnyFile-(\\d+\\.\\d+)(-ios)?\\.js$","minVersion":"3.1","baseDir":"js","mandatoryUpdate":true}
|
|
20
|
+
]
|
|
21
|
+
}
|