adapt-cli 2.1.13 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -0
- package/.eslintrc.json +14 -0
- package/README.md +150 -150
- package/bin/adapt.js +3 -0
- package/json/help-create/question.json +9 -0
- package/json/help-create.json +2 -1
- package/lib/api.js +260 -0
- package/lib/cli.js +61 -44
- package/lib/commands/authenticate.js +18 -0
- package/lib/commands/create/component.js +55 -72
- package/lib/commands/create/course.js +25 -80
- package/lib/commands/create/question.js +18 -0
- package/lib/commands/create.js +80 -85
- package/lib/commands/devinstall.js +35 -97
- package/lib/commands/help.js +31 -52
- package/lib/commands/install.js +16 -907
- package/lib/commands/ls.js +9 -24
- package/lib/commands/register.js +10 -195
- package/lib/commands/rename.js +13 -128
- package/lib/commands/search.js +10 -28
- package/lib/commands/uninstall.js +9 -136
- package/lib/commands/unregister.js +11 -95
- package/lib/commands/update.js +12 -867
- package/lib/commands/version.js +12 -14
- package/lib/integration/AdaptFramework/build.js +39 -0
- package/lib/integration/AdaptFramework/clone.js +27 -0
- package/lib/integration/AdaptFramework/deleteSrcCore.js +9 -0
- package/lib/integration/AdaptFramework/deleteSrcCourse.js +9 -0
- package/lib/integration/AdaptFramework/download.js +21 -0
- package/lib/integration/AdaptFramework/erase.js +34 -0
- package/lib/integration/AdaptFramework/getLatestVersion.js +79 -0
- package/lib/integration/AdaptFramework/npmInstall.js +21 -0
- package/lib/integration/AdaptFramework.js +19 -0
- package/lib/integration/Plugin.js +403 -0
- package/lib/integration/PluginManagement/autenticate.js +56 -0
- package/lib/integration/PluginManagement/install.js +222 -0
- package/lib/integration/PluginManagement/print.js +52 -0
- package/lib/integration/PluginManagement/register.js +130 -0
- package/lib/integration/PluginManagement/rename.js +101 -0
- package/lib/integration/PluginManagement/schemas.js +8 -0
- package/lib/integration/PluginManagement/search.js +46 -0
- package/lib/integration/PluginManagement/uninstall.js +141 -0
- package/lib/integration/PluginManagement/unregister.js +101 -0
- package/lib/integration/PluginManagement/update.js +224 -0
- package/lib/integration/PluginManagement.js +21 -0
- package/lib/integration/Project.js +146 -0
- package/lib/integration/Target.js +296 -0
- package/lib/integration/getBowerRegistryConfig.js +34 -0
- package/lib/logger.js +28 -0
- package/lib/util/JSONReadValidate.js +34 -0
- package/lib/util/constants.js +38 -0
- package/lib/util/createPromptTask.js +7 -0
- package/lib/util/download.js +45 -0
- package/lib/util/errors.js +58 -0
- package/lib/util/extract.js +24 -0
- package/lib/util/getDirNameFromImportMeta.js +6 -0
- package/lib/util/promises.js +36 -0
- package/package.json +20 -29
- package/TESTING.md +0 -25
- package/bin/adapt +0 -3
- package/gruntfile.js +0 -18
- package/lib/AdaptConsoleApplication.js +0 -19
- package/lib/CommandParser.js +0 -19
- package/lib/CommandTranslator.js +0 -16
- package/lib/ConsoleRenderer.js +0 -10
- package/lib/Constants.js +0 -68
- package/lib/JsonLoader.js +0 -40
- package/lib/JsonWriter.js +0 -21
- package/lib/PackageMeta.js +0 -41
- package/lib/Plugin.js +0 -53
- package/lib/PluginTypeResolver.js +0 -47
- package/lib/Project.js +0 -89
- package/lib/RendererHelpers.js +0 -41
- package/lib/RepositoryDownloader.js +0 -64
- package/lib/Slug.js +0 -5
- package/lib/VersionChecker.js +0 -7
- package/lib/commands/create/index.js +0 -6
- package/lib/commands/index.js +0 -16
- package/lib/commands/install/InstallLog.js +0 -32
- package/lib/commands/install/InstallTarget.js +0 -259
- package/lib/commands/install/extend.js +0 -31
- package/lib/download.js +0 -101
- package/lib/errors.js +0 -58
- package/lib/promise/authenticate.js +0 -58
- package/lib/promise/build.js +0 -20
- package/lib/promise/cloneInstall.js +0 -35
- package/lib/promise/confirmBuild.js +0 -6
- package/lib/promise/exec.js +0 -39
- package/lib/promise/getRepository.js +0 -26
- package/lib/promise/highest.js +0 -109
- package/lib/promise/install.js +0 -31
- package/lib/promise/installAdaptDependencies.js +0 -30
- package/lib/promise/installNodeDependencies.js +0 -28
- package/lib/promise/removeTemporaryDownload.js +0 -8
- package/lib/promise/replaceTextContent.js +0 -10
- package/lib/promise/uninstallPackage.js +0 -15
- package/lib/promise/update.js +0 -33
- package/lib/promise/util.js +0 -16
- package/test/fixtures/adapt-with-plugins.json +0 -6
- package/test/specs/command_translation_concerns.js +0 -13
- package/test/specs/create_command_concerns.js +0 -22
- package/test/specs/create_concerns.js +0 -30
- package/test/specs/install_concerns.js +0 -31
- package/test/specs/installing_compatible_plugins_concerns.js +0 -126
- package/test/specs/installing_incompatible_plugins_concerns.js +0 -103
- package/test/specs/ls_concerns.js +0 -28
- package/test/specs/plugin_name_concerns.js +0 -82
- package/test/specs/project_concerns.js +0 -128
- package/test/specs/registration_concerns.js +0 -31
- package/test/specs/repository_downloader_concerns.js +0 -55
- package/test/specs/search_concerns.js +0 -30
- package/test/specs/type_resolution_concerns.js +0 -71
- package/test/specs/uninstall_command_concerns.js +0 -64
- package/test/specs/uninstall_concerns.js +0 -31
package/lib/commands/update.js
CHANGED
@@ -1,867 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
var Plugin = require('../Plugin');
|
14
|
-
var Project = require('../Project');
|
15
|
-
var PluginTypeResolver = require('../PluginTypeResolver');
|
16
|
-
var RendererHelpers = require('../RendererHelpers');
|
17
|
-
var VersionChecker = require('../VersionChecker')
|
18
|
-
var update = require('../promise/update');
|
19
|
-
var promise = require('../promise/util');
|
20
|
-
var Errors = require('../errors');
|
21
|
-
var readline = require('readline');
|
22
|
-
|
23
|
-
module.exports = function() {
|
24
|
-
|
25
|
-
// # Assumptions
|
26
|
-
|
27
|
-
// All plugins are from Adapt ecosystem ("adapt-")
|
28
|
-
// As normal, .bowerrc will be read if present - this should point to a single Adapt registry
|
29
|
-
|
30
|
-
// # Tasks
|
31
|
-
|
32
|
-
// 1. Consider remove `project` and instead just store framework version
|
33
|
-
|
34
|
-
// standard output
|
35
|
-
var logger;
|
36
|
-
// our temporary bower manifest
|
37
|
-
var bowerJson;
|
38
|
-
// a representation of the Adapt project we are going to update
|
39
|
-
var project;
|
40
|
-
// the plugins to update (`Plugin` instances) with the target version
|
41
|
-
var plugins;
|
42
|
-
// whether to summarise installed plugins without modifying anything
|
43
|
-
var isCheck = false;
|
44
|
-
// whether to output debugging information or not
|
45
|
-
var isDebuggingEnabled = false;
|
46
|
-
// when a bower command errors this is the maximum number of attempts the command will be repeated
|
47
|
-
var bowerCmdMaxTry = 5;
|
48
|
-
var installedPlugins;
|
49
|
-
// whether this command is being performed on the command line
|
50
|
-
var isInteractive = true;
|
51
|
-
|
52
|
-
return {
|
53
|
-
apiupdate: function(pluginName, cwd) {
|
54
|
-
isInteractive = false;
|
55
|
-
|
56
|
-
Constants.setCwd(cwd);
|
57
|
-
|
58
|
-
clean();
|
59
|
-
|
60
|
-
bowerJson = {"name": "manifest", "dependencies":{}};
|
61
|
-
project = new Project(Constants.DefaultProjectManifestPath, Constants.DefaultProjectFrameworkPath);
|
62
|
-
plugins = [];
|
63
|
-
installedPlugins = {};
|
64
|
-
|
65
|
-
if (!project.isProjectContainsManifestFile()) {
|
66
|
-
return Q.reject(Errors.ERROR_COURSE_DIR);
|
67
|
-
}
|
68
|
-
|
69
|
-
args = pluginName ? [pluginName] : ['all'];
|
70
|
-
|
71
|
-
discoverPlugins();
|
72
|
-
|
73
|
-
return Q(args)
|
74
|
-
.then(createManifestFromArguments)
|
75
|
-
.then(checkRedundancy)
|
76
|
-
.then(createPlugins)
|
77
|
-
.then(determineTargetVersions)
|
78
|
-
.then(checkIncompatible)
|
79
|
-
.then(performUpdates)
|
80
|
-
.then(verifyChanged)
|
81
|
-
.then(printUpdateSummary)
|
82
|
-
.finally(clean);
|
83
|
-
},
|
84
|
-
update: function(renderer) {
|
85
|
-
var args = [].slice.call(arguments, 1);
|
86
|
-
var done = args.pop() || function() {};
|
87
|
-
|
88
|
-
logger = renderer;
|
89
|
-
|
90
|
-
clean();
|
91
|
-
|
92
|
-
bowerJson = {"name": "manifest", "dependencies":{}};
|
93
|
-
project = new Project(Constants.DefaultProjectManifestPath, Constants.DefaultProjectFrameworkPath);
|
94
|
-
plugins = [];
|
95
|
-
installedPlugins = {};
|
96
|
-
|
97
|
-
//bower.commands.info('adapt-contrib-media').on('end', function() {console.log(arguments)})
|
98
|
-
|
99
|
-
var checkArgIndex = args.indexOf('--check');
|
100
|
-
var debugArgIndex = args.indexOf('--debug');
|
101
|
-
|
102
|
-
if (checkArgIndex >= 0) {
|
103
|
-
args.splice(checkArgIndex, 1);
|
104
|
-
isCheck = true;
|
105
|
-
}
|
106
|
-
|
107
|
-
if (debugArgIndex >= 0) {
|
108
|
-
args.splice(checkArgIndex, 1);
|
109
|
-
isDebuggingEnabled = true;
|
110
|
-
}
|
111
|
-
|
112
|
-
discoverPlugins();
|
113
|
-
|
114
|
-
if (isCheck) {
|
115
|
-
init(args)
|
116
|
-
.then(checkRedundancy)
|
117
|
-
.then(createPlugins)
|
118
|
-
.then(determineTargetVersions)
|
119
|
-
.then(printCheckSummary)
|
120
|
-
.then(done)
|
121
|
-
.fail(reportFailure(logger, done))
|
122
|
-
} else {
|
123
|
-
init(args)
|
124
|
-
.then(checkRedundancy)
|
125
|
-
.then(createPlugins)
|
126
|
-
.then(determineTargetVersions)
|
127
|
-
.then(checkMissing)
|
128
|
-
.then(promptToUpdateIncompatible)
|
129
|
-
.then(performUpdates)
|
130
|
-
.then(verifyChanged)
|
131
|
-
.then(printUpdateSummary)
|
132
|
-
.then(done)
|
133
|
-
.fail(reportFailure(logger, done))
|
134
|
-
.finally(clean);
|
135
|
-
}
|
136
|
-
}
|
137
|
-
}
|
138
|
-
|
139
|
-
function discoverPlugins() {
|
140
|
-
|
141
|
-
var components = discoverNamedGroup('components');
|
142
|
-
var extensions = discoverNamedGroup('extensions');
|
143
|
-
var menu = discoverNamedGroup('menu');
|
144
|
-
var theme = discoverNamedGroup('theme');
|
145
|
-
|
146
|
-
function discoverNamedGroup(group) {
|
147
|
-
var srcpath = path.join(Constants.cwd, 'src', group);
|
148
|
-
|
149
|
-
if (!fs.existsSync(srcpath)) return;
|
150
|
-
|
151
|
-
var pluginNames = [];
|
152
|
-
|
153
|
-
fs.readdirSync(srcpath).forEach(function(f) {
|
154
|
-
var pluginPath = path.join(srcpath, f);
|
155
|
-
var bowerPath = path.join(pluginPath, 'bower.json');
|
156
|
-
var bowerManifest;
|
157
|
-
|
158
|
-
if (fs.lstatSync(pluginPath).isDirectory() && fs.existsSync(bowerPath)) {
|
159
|
-
bowerManifest = JsonLoader.readJSONSync(bowerPath);
|
160
|
-
if (bowerManifest.name) {
|
161
|
-
installedPlugins[bowerManifest.name] = {manifest:bowerManifest, group:group};
|
162
|
-
}
|
163
|
-
}
|
164
|
-
});
|
165
|
-
}
|
166
|
-
}
|
167
|
-
|
168
|
-
function addSelectedPlugins(arr) {
|
169
|
-
var groups = ['all', 'components', 'extensions', 'menu', 'theme'];
|
170
|
-
var selectedGroups = [];
|
171
|
-
|
172
|
-
// record which groups are found and remove from list, taking care to avoid duplicates
|
173
|
-
arr = arr.filter(function(item) {
|
174
|
-
if (groups.indexOf(item) != -1) {
|
175
|
-
if (selectedGroups.indexOf(item) == -1) selectedGroups.push(item);
|
176
|
-
return false;
|
177
|
-
}
|
178
|
-
return true;
|
179
|
-
});
|
180
|
-
|
181
|
-
if (selectedGroups.indexOf('all') != -1) {
|
182
|
-
addAllPlugins();
|
183
|
-
} else {
|
184
|
-
// add components, extensions, menus etc
|
185
|
-
selectedGroups.forEach(addPluginsFromGroup);
|
186
|
-
// add individual plugins
|
187
|
-
arr.forEach(addPlugin);
|
188
|
-
}
|
189
|
-
}
|
190
|
-
|
191
|
-
function getPluginNames(group) {
|
192
|
-
return _.filter(_.keys(installedPlugins), function(k) {
|
193
|
-
return installedPlugins[k].group == group;
|
194
|
-
});
|
195
|
-
}
|
196
|
-
|
197
|
-
function addPlugin(arg) {
|
198
|
-
var tokens = arg.split(/[#@]/);
|
199
|
-
var name = tokens[0];
|
200
|
-
var version = tokens[1];
|
201
|
-
|
202
|
-
if (!installedPlugins[name]) return;
|
203
|
-
|
204
|
-
switch (tokens.length) {
|
205
|
-
case 1: bowerJson.dependencies[name] = '*'; break;
|
206
|
-
case 2: bowerJson.dependencies[name] = version; break;
|
207
|
-
default: return;
|
208
|
-
}
|
209
|
-
}
|
210
|
-
|
211
|
-
function addPluginsFromGroup(group) {
|
212
|
-
var all = !group || group == 'all';
|
213
|
-
|
214
|
-
if (group == 'components' || all) getPluginNames('components').forEach(addPlugin);
|
215
|
-
if (group == 'extensions' || all) getPluginNames('extensions').forEach(addPlugin);
|
216
|
-
if (group == 'menu' || all) getPluginNames('menu').forEach(addPlugin);
|
217
|
-
if (group == 'theme' || all) getPluginNames('theme').forEach(addPlugin);
|
218
|
-
}
|
219
|
-
|
220
|
-
function addAllPlugins() {
|
221
|
-
addPluginsFromGroup();
|
222
|
-
}
|
223
|
-
|
224
|
-
function createManifestFromArguments(args) {
|
225
|
-
addSelectedPlugins(args);
|
226
|
-
}
|
227
|
-
|
228
|
-
function init(args) {
|
229
|
-
logger.log();
|
230
|
-
|
231
|
-
if (args.length == 0) {
|
232
|
-
if (isCheck) {
|
233
|
-
args = ['all'];
|
234
|
-
return Q(args).then(createManifestFromArguments);
|
235
|
-
}
|
236
|
-
|
237
|
-
return createPromptTask({
|
238
|
-
message: chalk.reset('This command will attempt to update all installed plugins. Do you wish to continue?'),
|
239
|
-
type: 'confirm'
|
240
|
-
}).then(function() {
|
241
|
-
args = ['all'];
|
242
|
-
return Q(args).then(createManifestFromArguments);
|
243
|
-
});
|
244
|
-
}
|
245
|
-
// else process arguments
|
246
|
-
return Q(args).then(createManifestFromArguments);
|
247
|
-
}
|
248
|
-
|
249
|
-
function checkRedundancy() {
|
250
|
-
if (Object.keys(bowerJson.dependencies).length == 0) {
|
251
|
-
if (isInteractive) {
|
252
|
-
return Q.reject({message:'No valid targets specified (please check spelling and case).'});
|
253
|
-
}
|
254
|
-
return Q.reject(Errors.ERROR_NOTHING_TO_UPDATE);
|
255
|
-
} else {
|
256
|
-
return Q.resolve();
|
257
|
-
}
|
258
|
-
}
|
259
|
-
|
260
|
-
function createPlugins() {
|
261
|
-
debug('createPlugins');
|
262
|
-
|
263
|
-
Object.keys(bowerJson.dependencies).forEach(function(pluginName) {
|
264
|
-
var plugin = Plugin.parse(pluginName+'#'+bowerJson.dependencies[pluginName]);
|
265
|
-
plugin._installedVersion = installedPlugins[pluginName].manifest.version;
|
266
|
-
plugin._versionIndex = 0;
|
267
|
-
plugin._bowerCmdCount = 0;
|
268
|
-
plugin._belongsTo = installedPlugins[pluginName].group;
|
269
|
-
plugins.push(plugin);
|
270
|
-
});
|
271
|
-
|
272
|
-
var promiseToGetInfo = [];
|
273
|
-
|
274
|
-
for (var i=0, c=plugins.length; i<c; i++) {
|
275
|
-
promiseToGetInfo.push(getInfo(plugins[i]));
|
276
|
-
}
|
277
|
-
|
278
|
-
if (!isInteractive) {
|
279
|
-
return Q.all(promiseToGetInfo);
|
280
|
-
}
|
281
|
-
|
282
|
-
return Q.all(promiseToGetInfo).progress(function() {
|
283
|
-
var settled = plugins.filter(function(plugin) {return plugin._bowerInfo || plugin._isMissingAtRepo;}).length;
|
284
|
-
var total = plugins.length;
|
285
|
-
readline.cursorTo(process.stderr, 0);
|
286
|
-
process.stderr.write(chalk.bold.cyan('<info>')+' Querying server '+Math.round(100*settled/total)+'% complete');
|
287
|
-
})
|
288
|
-
.then(function() {
|
289
|
-
process.stderr.write('\n');
|
290
|
-
});
|
291
|
-
}
|
292
|
-
|
293
|
-
function determineTargetVersions() {
|
294
|
-
//console.log('determineTargetVersions');
|
295
|
-
return Q.all(plugins.filter(isPresent).map(getTargetVersion));
|
296
|
-
}
|
297
|
-
|
298
|
-
function getTargetVersion(plugin) {
|
299
|
-
plugin._latestVersion = plugin._bowerInfo.version;
|
300
|
-
|
301
|
-
// if the plugin has no tags then it is not possible to change version
|
302
|
-
if (!plugin._versions || plugin._versions.length == 0) return Q.resolve();
|
303
|
-
|
304
|
-
// if plugin already at latest version then nothing to do
|
305
|
-
if (semver.satisfies(plugin._installedVersion, plugin._bowerInfo.version)) {
|
306
|
-
//console.log('no update available for', plugin.packageName, plugin._bowerInfo.version);
|
307
|
-
plugin._isAtLatestVersion = true;
|
308
|
-
return Q.resolve();
|
309
|
-
}
|
310
|
-
|
311
|
-
//console.log('checking available updates for', plugin.packageName, 'with constraint', plugin.version, '(latest version is '+plugin._bowerInfo.version+')');
|
312
|
-
|
313
|
-
return checkProposedVersion(plugin);
|
314
|
-
}
|
315
|
-
|
316
|
-
function checkProposedVersion(plugin, deferred) {
|
317
|
-
deferred = deferred || Q.defer();
|
318
|
-
var adaptVersion = project.getFrameworkVersion();
|
319
|
-
var satisfiesConstraint = semver.satisfies(plugin._bowerInfo.version, plugin.version);
|
320
|
-
|
321
|
-
//console.log('getting target version for', plugin.packageName, ': checking', plugin._versions[plugin._versionIndex]);
|
322
|
-
|
323
|
-
if (!plugin._isMissingAtRepo) {
|
324
|
-
//console.log('plugin not missing, plugin framework requirement is', plugin._bowerInfo.framework, 'installed framework', adaptVersion);
|
325
|
-
// check that the proposed plugin is compatible with the installed framework and that it also satisfies any user-provided constraint
|
326
|
-
if (semver.satisfies(adaptVersion, plugin._bowerInfo.framework) &&
|
327
|
-
satisfiesConstraint) {
|
328
|
-
//console.log(plugin.packageName, chalk.green('can'), 'be updated from', plugin._installedVersion, 'to', plugin._bowerInfo.version, '(requires framework '+plugin._bowerInfo.framework+')');
|
329
|
-
plugin._proposedVersion = plugin._bowerInfo.version;
|
330
|
-
plugin._shouldBeUpdated = true;
|
331
|
-
deferred.resolve();
|
332
|
-
} else {
|
333
|
-
//console.log(plugin.packageName, chalk.red('cannot'), 'be updated to', plugin._bowerInfo.version, '(requires framework'+plugin._bowerInfo.framework+')');
|
334
|
-
if (plugin._versionIndex + 1 < plugin._versions.length && semver.gt(plugin._versions[plugin._versionIndex + 1], plugin._installedVersion)) {
|
335
|
-
plugin._versionIndex++;
|
336
|
-
getInfo(plugin).then(function() {
|
337
|
-
checkProposedVersion(plugin, deferred);
|
338
|
-
});
|
339
|
-
} else {
|
340
|
-
deferred.resolve();
|
341
|
-
}
|
342
|
-
}
|
343
|
-
} else {
|
344
|
-
deferred.resolve();
|
345
|
-
}
|
346
|
-
|
347
|
-
return deferred.promise;
|
348
|
-
}
|
349
|
-
|
350
|
-
function getInfo(plugin, deferred) {
|
351
|
-
// presence of deferred signifies a retry
|
352
|
-
if (!deferred) this._bowerCmdCount = 0;
|
353
|
-
|
354
|
-
deferred = deferred || Q.defer();
|
355
|
-
|
356
|
-
function onSuccess(results) {
|
357
|
-
plugin._bowerInfo = results.latest || results;
|
358
|
-
if (results.versions) plugin._versions = results.versions;
|
359
|
-
deferred.notify();
|
360
|
-
deferred.resolve(results);
|
361
|
-
}
|
362
|
-
|
363
|
-
function onFail() {
|
364
|
-
reportError();
|
365
|
-
|
366
|
-
if (canRetry()) {
|
367
|
-
getInfo(plugin, deferred);
|
368
|
-
} else {
|
369
|
-
plugin._isMissingAtRepo = true;
|
370
|
-
deferred.notify();
|
371
|
-
deferred.resolve();
|
372
|
-
}
|
373
|
-
}
|
374
|
-
|
375
|
-
try {
|
376
|
-
//console.log('Querying registry for', plugin.packageName, '(' + plugin.version + ')');
|
377
|
-
var versionString = plugin._versions ? '#'+plugin._versions[plugin._versionIndex] : '';
|
378
|
-
plugin._bowerCmdCount++;
|
379
|
-
bower.commands.info(plugin.packageName+versionString, null, {registry:Constants.getRegistry(), cwd:Constants.cwd}).on('end', onSuccess).on('error', onFail);
|
380
|
-
} catch(err) {
|
381
|
-
onFail();
|
382
|
-
}
|
383
|
-
|
384
|
-
function canRetry() {
|
385
|
-
return plugin._bowerCmdCount < bowerCmdMaxTry;
|
386
|
-
}
|
387
|
-
|
388
|
-
function reportError() {
|
389
|
-
if (plugin._bowerCmdCount < bowerCmdMaxTry) {
|
390
|
-
debug(chalk.bold.magenta('<debug>'), 'Could not get info for', plugin.packageName+'.', 'Retrying.');
|
391
|
-
} else {
|
392
|
-
debug(chalk.bold.magenta('<debug>'), 'Could not get info for', plugin.packageName+'.', 'Aborting.');
|
393
|
-
}
|
394
|
-
}
|
395
|
-
|
396
|
-
return deferred.promise;
|
397
|
-
}
|
398
|
-
|
399
|
-
function checkMissing() {
|
400
|
-
var missing = plugins.filter(isMissing);
|
401
|
-
|
402
|
-
if (missing.length == 0) {
|
403
|
-
return Q.resolve();
|
404
|
-
} else if (missing.length == plugins.length) {
|
405
|
-
if (missing.length == 1) {
|
406
|
-
return Q.reject('The requested plugin was not found at the registry');
|
407
|
-
} else {
|
408
|
-
return Q.reject('None of the requested plugins were found at the registry');
|
409
|
-
}
|
410
|
-
} else {
|
411
|
-
return promptToListMissing().then(listMissingAndPromptToContinue);
|
412
|
-
}
|
413
|
-
}
|
414
|
-
|
415
|
-
function promptToListMissing() {
|
416
|
-
return createPromptTask({
|
417
|
-
message: chalk.cyan('Some plugins could not be found at the registry. Hit <Enter> for list.'),
|
418
|
-
type: 'confirm'
|
419
|
-
});
|
420
|
-
}
|
421
|
-
|
422
|
-
function listMissingAndPromptToContinue() {
|
423
|
-
var missing = plugins.filter(isMissing);
|
424
|
-
|
425
|
-
missing.forEach(function(plugin) {
|
426
|
-
logger.log(plugin.packageName);
|
427
|
-
});
|
428
|
-
|
429
|
-
return createPromptTask({
|
430
|
-
message: chalk.cyan('Continue to update other plugins?'),
|
431
|
-
type: 'confirm',
|
432
|
-
default: true
|
433
|
-
});
|
434
|
-
}
|
435
|
-
|
436
|
-
function isMissing(plugin) {
|
437
|
-
return plugin._isMissingAtRepo === true;
|
438
|
-
}
|
439
|
-
|
440
|
-
function isPresent(plugin) {
|
441
|
-
return !isMissing(plugin);
|
442
|
-
}
|
443
|
-
|
444
|
-
function isIncompatible(plugin) {
|
445
|
-
return !semver.valid(plugin._proposedVersion);
|
446
|
-
}
|
447
|
-
|
448
|
-
function isToBeUpdated(plugin) {
|
449
|
-
return plugin._shouldBeUpdated && !plugin._wasUpdated;
|
450
|
-
}
|
451
|
-
|
452
|
-
function isConstrained(plugin) {
|
453
|
-
return plugin.version != '*';
|
454
|
-
}
|
455
|
-
|
456
|
-
function isUntagged(plugin) {
|
457
|
-
return !plugin._versions || plugin._versions.length == 0;
|
458
|
-
}
|
459
|
-
|
460
|
-
function someOtherVersionSatisfiesConstraint(plugin) {
|
461
|
-
var maxSatisfying = semver.maxSatisfying(plugin._versions, plugin.version);
|
462
|
-
return maxSatisfying != null && !semver.satisfies(maxSatisfying, plugin._installedVersion);
|
463
|
-
}
|
464
|
-
|
465
|
-
function checkIncompatible() {
|
466
|
-
var list = plugins.filter(isPresent).filter(isIncompatible).filter(isConstrained).filter(someOtherVersionSatisfiesConstraint);
|
467
|
-
|
468
|
-
if (list.length == 0) return Q.resolve();
|
469
|
-
|
470
|
-
var names = list.map(function(p) {
|
471
|
-
return p.packageName;
|
472
|
-
});
|
473
|
-
|
474
|
-
return Q.reject(Errors.ERROR_UPDATE_INCOMPATIBLE);
|
475
|
-
}
|
476
|
-
|
477
|
-
function promptToUpdateIncompatible() {
|
478
|
-
//console.log('promptToUpdateIncompatible');
|
479
|
-
var adaptVersion = project.getFrameworkVersion();
|
480
|
-
// if there are no compatible updates but the user has requested a specific version (or range) and a corresponding version exists then prompt
|
481
|
-
var list = plugins.filter(isPresent).filter(isIncompatible).filter(isConstrained).filter(someOtherVersionSatisfiesConstraint);
|
482
|
-
|
483
|
-
if (list.length == 0) return Q.resolve();
|
484
|
-
|
485
|
-
logger.log(chalk.bgRed('<warning>'), ' Changes to the following plugins have been requested that will not use the latest compatible version in each case.');
|
486
|
-
|
487
|
-
return promise.serialise(list, function(plugin) {
|
488
|
-
// only prompt for plugins that have been requsted with a specific version constraint by the user
|
489
|
-
return createPromptTask({
|
490
|
-
message: chalk.reset(`Change ${plugin.packageName} to ${semver.maxSatisfying(plugin._versions, plugin.version)}?`),
|
491
|
-
type: 'confirm',
|
492
|
-
default: false,
|
493
|
-
onlyRejectOnError: true
|
494
|
-
})
|
495
|
-
.then(function(result) {
|
496
|
-
plugin._shouldBeUpdated = result;
|
497
|
-
});
|
498
|
-
});
|
499
|
-
}
|
500
|
-
|
501
|
-
function performUpdates() {
|
502
|
-
var filtered = plugins.filter(isPresent).filter(isToBeUpdated);
|
503
|
-
var settled = 0, total = filtered.length;
|
504
|
-
|
505
|
-
return promise.serialise(filtered, function(plugin) {
|
506
|
-
return createUpdateTask(plugin);
|
507
|
-
})
|
508
|
-
.then(function() {
|
509
|
-
if (isInteractive) renderUpdateProgressFinished();
|
510
|
-
});
|
511
|
-
}
|
512
|
-
|
513
|
-
function verifyChanged() {
|
514
|
-
plugins.filter(isPresent).forEach(function(plugin) {
|
515
|
-
if (!plugin._wasUpdated) return;
|
516
|
-
|
517
|
-
var p = path.join(Constants.cwd, 'src', plugin._belongsTo, plugin.packageName, 'bower.json');
|
518
|
-
|
519
|
-
plugin._bowerInfo = JsonLoader.readJSONSync(p);
|
520
|
-
plugin._updatedVersion = plugin._bowerInfo.version;
|
521
|
-
});
|
522
|
-
|
523
|
-
return Q.resolve();
|
524
|
-
}
|
525
|
-
|
526
|
-
function highlight(str) {
|
527
|
-
var sub1 = 'adapt-contrib-';
|
528
|
-
var sub2 = 'adapt-';
|
529
|
-
|
530
|
-
if (str.indexOf(sub1) == 0) {
|
531
|
-
return chalk.reset(sub1)+chalk.yellowBright(str.substring(sub1.length));
|
532
|
-
}
|
533
|
-
|
534
|
-
if (str.indexOf(sub2) == 0) {
|
535
|
-
return chalk.reset(sub2)+chalk.yellowBright(str.substring(sub2.length));
|
536
|
-
}
|
537
|
-
|
538
|
-
return str;
|
539
|
-
}
|
540
|
-
|
541
|
-
function yellowIfEqual(v1, v2) {
|
542
|
-
var colourFunc = semver.satisfies(v1, v2) ? chalk.yellowBright : chalk.magentaBright;
|
543
|
-
|
544
|
-
return colourFunc(v2);
|
545
|
-
}
|
546
|
-
|
547
|
-
function greenIfEqual(v1, v2) {
|
548
|
-
var colourFunc = semver.satisfies(v1, v2) ? chalk.greenBright : chalk.magentaBright;
|
549
|
-
|
550
|
-
return colourFunc(v2);
|
551
|
-
}
|
552
|
-
|
553
|
-
function printCheckSummary() {
|
554
|
-
//console.log('printCheckSummary');
|
555
|
-
|
556
|
-
var present = plugins.filter(isPresent);
|
557
|
-
var missing = plugins.filter(isMissing);
|
558
|
-
var untagged = _.difference(present.filter(isUntagged), isMissing);
|
559
|
-
var latest = present.filter(function(plugin) {return plugin._isAtLatestVersion});
|
560
|
-
var updateAvailable = present.filter(function(plugin){return plugin._proposedVersion});
|
561
|
-
var updateNotAvailable = _.difference(present.filter(function(plugin){return !plugin._proposedVersion}), missing, untagged, latest);
|
562
|
-
|
563
|
-
var byPackageName = function(a, b) {
|
564
|
-
if (a.packageName < b.packageName) return -1;
|
565
|
-
if (a.packageName > b.packageName) return 1;
|
566
|
-
return 0;
|
567
|
-
};
|
568
|
-
|
569
|
-
logger.log();
|
570
|
-
|
571
|
-
if (latest.length > 0) logger.log(chalk.whiteBright('The following plugins are using the latest version:'));
|
572
|
-
|
573
|
-
latest.sort(byPackageName).forEach(function(plugin) {
|
574
|
-
logger.log(chalk.reset(highlight(plugin.packageName+' @'+plugin._installedVersion)));
|
575
|
-
});
|
576
|
-
|
577
|
-
if (latest.length > 0) logger.log();
|
578
|
-
|
579
|
-
// ************************************
|
580
|
-
|
581
|
-
if (updateAvailable.length > 0) logger.log(chalk.whiteBright('The following updates can be made:'));
|
582
|
-
|
583
|
-
updateAvailable.sort(byPackageName).forEach(function(plugin) {
|
584
|
-
logger.log(chalk.reset(highlight(plugin.packageName), 'from', chalk.yellowBright(plugin._installedVersion), 'to', chalk.greenBright(plugin._proposedVersion), '(latest is '+greenIfEqual(plugin._proposedVersion, plugin._latestVersion)+')'));
|
585
|
-
});
|
586
|
-
|
587
|
-
if (updateAvailable.length > 0) logger.log();
|
588
|
-
|
589
|
-
// ************************************
|
590
|
-
|
591
|
-
if (updateNotAvailable.length > 0) logger.log(chalk.whiteBright('The following have no compatible updates:'));
|
592
|
-
|
593
|
-
updateNotAvailable.sort(byPackageName).forEach(function(plugin) {
|
594
|
-
logger.log(chalk.reset(highlight(plugin.packageName+' @'+plugin._installedVersion)+' (latest is '+chalk.magentaBright(plugin._latestVersion)+')'));
|
595
|
-
});
|
596
|
-
|
597
|
-
if (updateNotAvailable.length > 0) logger.log();
|
598
|
-
|
599
|
-
// ************************************
|
600
|
-
|
601
|
-
untagged.sort(byPackageName).forEach(function(plugin) {
|
602
|
-
logger.log(chalk.redBright(plugin.packageName, 'has no version tags and so cannot be updated (use adapt install', plugin.packageName, 'to overwrite)'));
|
603
|
-
});
|
604
|
-
|
605
|
-
if (untagged.length > 0) logger.log();
|
606
|
-
|
607
|
-
// ************************************
|
608
|
-
|
609
|
-
missing.sort(byPackageName).forEach(function(plugin) {
|
610
|
-
logger.log(chalk.redBright(plugin.packageName, 'could not be found at the registry'));
|
611
|
-
});
|
612
|
-
|
613
|
-
if (missing.length > 0) logger.log();
|
614
|
-
}
|
615
|
-
|
616
|
-
function printUpdateSummary() {
|
617
|
-
if (isInteractive) logger.log(chalk.bold.cyan('<info>'), 'Operation completed. Update summary:');
|
618
|
-
|
619
|
-
var present = plugins.filter(isPresent);
|
620
|
-
var missing = plugins.filter(isMissing);
|
621
|
-
var untagged = _.difference(present.filter(isUntagged), isMissing);
|
622
|
-
var errored = present.filter(function(plugin) {return plugin._shouldBeUpdated && !plugin._wasUpdated});
|
623
|
-
var updated = present.filter(function(plugin) {return plugin._wasUpdated});
|
624
|
-
var latest = present.filter(function(plugin) {return plugin._isAtLatestVersion});
|
625
|
-
var userSkipped = _.difference(present.filter(isConstrained).filter(isIncompatible).filter(someOtherVersionSatisfiesConstraint), updated, errored);
|
626
|
-
var incompatibleConstrained = _.difference(present.filter(isIncompatible).filter(isConstrained), updated, untagged);
|
627
|
-
var incompatible = _.difference(present.filter(isIncompatible), missing, untagged, latest, updated, incompatibleConstrained);
|
628
|
-
|
629
|
-
var byPackageName = function(a, b) {
|
630
|
-
if (a.packageName < b.packageName) return -1;
|
631
|
-
if (a.packageName > b.packageName) return 1;
|
632
|
-
return 0;
|
633
|
-
};
|
634
|
-
|
635
|
-
if (!isInteractive) {
|
636
|
-
var report = [];
|
637
|
-
|
638
|
-
if (plugins.length == 1) {
|
639
|
-
var p = plugins[0];
|
640
|
-
|
641
|
-
if (latest.length == 1 || updated.length == 1) {
|
642
|
-
var bowerPath = path.join(Constants.cwd, 'src', p._belongsTo, p.packageName, 'bower.json');
|
643
|
-
return Q.resolve(JsonLoader.readJSONSync(bowerPath));
|
644
|
-
}
|
645
|
-
if (errored.length == 1) {
|
646
|
-
var error = _.clone(Errors.ERROR_UPDATE_ERROR);
|
647
|
-
|
648
|
-
if (p._updateError) error.message = p._updateError;
|
649
|
-
|
650
|
-
return Q.reject(error);
|
651
|
-
}
|
652
|
-
if (incompatible.length == 1) {
|
653
|
-
return Q.reject(Errors.ERROR_NO_UPDATE);
|
654
|
-
}
|
655
|
-
if (untagged.length == 1) {
|
656
|
-
return Q.reject(Errors.ERROR_NO_RELEASES);
|
657
|
-
}
|
658
|
-
|
659
|
-
return Q.reject(Errors.ERROR_NOT_FOUND);
|
660
|
-
}
|
661
|
-
|
662
|
-
latest.forEach(function(p) {
|
663
|
-
report.push({
|
664
|
-
name: p.packageName,
|
665
|
-
status:'fulfilled',
|
666
|
-
pluginData: p._bowerInfo
|
667
|
-
});
|
668
|
-
});
|
669
|
-
|
670
|
-
updated.forEach(function(p) {
|
671
|
-
report.push({
|
672
|
-
name: p.packageName,
|
673
|
-
status:'fulfilled',
|
674
|
-
pluginData: p._bowerInfo
|
675
|
-
});
|
676
|
-
});
|
677
|
-
|
678
|
-
// N.B. there will not be any incompatibleConstrained as this results in a rejected promise
|
679
|
-
|
680
|
-
errored.forEach(function(p) {
|
681
|
-
var error = _.clone(Errors.ERROR_UPDATE_ERROR);
|
682
|
-
|
683
|
-
if (p._updateError) error.message = p._updateError;
|
684
|
-
|
685
|
-
report.push({
|
686
|
-
name: p.packageName,
|
687
|
-
status:'rejected',
|
688
|
-
pluginData: p._bowerInfo,
|
689
|
-
reason: error
|
690
|
-
});
|
691
|
-
});
|
692
|
-
|
693
|
-
incompatible.forEach(function(p) {
|
694
|
-
report.push({
|
695
|
-
name: p.packageName,
|
696
|
-
status:'rejected',
|
697
|
-
pluginData: p._bowerInfo,
|
698
|
-
reason: Errors.ERROR_NO_UPDATE
|
699
|
-
});
|
700
|
-
});
|
701
|
-
|
702
|
-
untagged.forEach(function(p) {
|
703
|
-
report.push({
|
704
|
-
name: p.packageName,
|
705
|
-
status:'rejected',
|
706
|
-
pluginData: p._bowerInfo,
|
707
|
-
reason: Errors.ERROR_NO_RELEASES
|
708
|
-
});
|
709
|
-
});
|
710
|
-
|
711
|
-
missing.forEach(function(p) {
|
712
|
-
report.push({
|
713
|
-
name: p.packageName,
|
714
|
-
status:'rejected',
|
715
|
-
pluginData: p._bowerInfo,
|
716
|
-
reason: Errors.ERROR_NOT_FOUND
|
717
|
-
});
|
718
|
-
});
|
719
|
-
|
720
|
-
return Q.resolve(report);
|
721
|
-
}
|
722
|
-
|
723
|
-
logger.log();
|
724
|
-
|
725
|
-
if (latest.length > 0) logger.log(chalk.whiteBright('The following plugins are using the latest version:'));
|
726
|
-
|
727
|
-
latest.sort(byPackageName).forEach(function(plugin) {
|
728
|
-
logger.log(chalk.reset(highlight(plugin.packageName+' @'+plugin._installedVersion)));
|
729
|
-
});
|
730
|
-
|
731
|
-
if (latest.length > 0) logger.log();
|
732
|
-
|
733
|
-
//***************************
|
734
|
-
|
735
|
-
if (incompatibleConstrained.length > 0) logger.log(chalk.whiteBright('The following plugins are using the requested version:'));
|
736
|
-
|
737
|
-
incompatibleConstrained.sort(byPackageName).forEach(function(plugin) {
|
738
|
-
logger.log(chalk.reset(highlight(plugin.packageName+' @'+plugin._installedVersion)) + '. Latest is', chalk.magentaBright(plugin._latestVersion));
|
739
|
-
});
|
740
|
-
|
741
|
-
if (incompatibleConstrained.length > 0) logger.log();
|
742
|
-
|
743
|
-
//***************************
|
744
|
-
|
745
|
-
if (incompatible.length > 0) logger.log(chalk.whiteBright('The following plugins are using the latest compatible version:'));
|
746
|
-
|
747
|
-
incompatible.sort(byPackageName).forEach(function(plugin) {
|
748
|
-
logger.log(chalk.reset(highlight(plugin.packageName+' @'+plugin._installedVersion)) + '. Latest is', greenIfEqual(plugin._installedVersion, plugin._latestVersion));
|
749
|
-
});
|
750
|
-
|
751
|
-
if (incompatible.length > 0) logger.log();
|
752
|
-
|
753
|
-
//***************************
|
754
|
-
|
755
|
-
if (updated.length > 0) logger.log(chalk.whiteBright('The following updates have been made:'));
|
756
|
-
|
757
|
-
updated.sort(byPackageName).forEach(function(plugin) {
|
758
|
-
logger.log(chalk.reset(highlight(plugin.packageName)), 'from', chalk.yellowBright(plugin._installedVersion), 'to', chalk.greenBright(plugin._updatedVersion)+'.', 'Latest is', greenIfEqual(plugin._updatedVersion, plugin._latestVersion));
|
759
|
-
});
|
760
|
-
|
761
|
-
if (updated.length > 0) logger.log();
|
762
|
-
|
763
|
-
//***************************
|
764
|
-
|
765
|
-
userSkipped.sort(byPackageName).forEach(function(plugin) {
|
766
|
-
logger.log(chalk.magenta(plugin.packageName, 'was skipped'));
|
767
|
-
});
|
768
|
-
|
769
|
-
if (userSkipped.length > 0) logger.log();
|
770
|
-
|
771
|
-
errored.sort(byPackageName).forEach(function(plugin) {
|
772
|
-
logger.log(chalk.bold.redBright(plugin.packageName, 'could not be updated', '(error code '+plugin._updateError+')'));
|
773
|
-
});
|
774
|
-
|
775
|
-
if (errored.length > 0) logger.log();
|
776
|
-
|
777
|
-
untagged.sort(byPackageName).forEach(function(plugin) {
|
778
|
-
logger.log(chalk.redBright(plugin.packageName, 'has no version tags and so cannot be updated (use adapt install', plugin.packageName, 'to overwrite)'));
|
779
|
-
});
|
780
|
-
|
781
|
-
if (untagged.length > 0) logger.log();
|
782
|
-
|
783
|
-
missing.sort(byPackageName).sort(byPackageName).forEach(function(plugin) {
|
784
|
-
logger.log(chalk.redBright(plugin.packageName, 'could not be found at the registry'));
|
785
|
-
});
|
786
|
-
|
787
|
-
return Q.resolve();
|
788
|
-
}
|
789
|
-
|
790
|
-
function clean() {
|
791
|
-
if (fs.existsSync(path.join(Constants.cwd, 'bower.json'))) {
|
792
|
-
fs.unlinkSync(path.join(Constants.cwd, 'bower.json'));
|
793
|
-
}
|
794
|
-
return Q.resolve();
|
795
|
-
}
|
796
|
-
|
797
|
-
function createUpdateTask(plugin) {
|
798
|
-
//console.log(plugin.packageName, 'is missing', !!plugin._isMissingAtRepo, 'is ignored',!plugin._shouldBeUpdated);
|
799
|
-
|
800
|
-
return Q.when(null, function() {
|
801
|
-
var deps = {};
|
802
|
-
var manifest;
|
803
|
-
|
804
|
-
// create bower.json with a single dependency, otherwise bower will install things incorrectly
|
805
|
-
deps[plugin.packageName] = plugin._proposedVersion || plugin.version;
|
806
|
-
manifest = _.extend({}, bowerJson, {dependencies:deps});
|
807
|
-
|
808
|
-
//console.log('manifest\n', JSON.stringify(manifest, null, 4));
|
809
|
-
JsonWriter.writeJSONSync(path.join(Constants.cwd, 'bower.json'), manifest);
|
810
|
-
//console.log(JSON.stringify(JsonLoader.readJSONSync('bower.json'), null, 4));
|
811
|
-
return update(plugin, null, {
|
812
|
-
directory: path.join('src', plugin._belongsTo),
|
813
|
-
registry: Constants.getRegistry(),
|
814
|
-
cwd:Constants.cwd,
|
815
|
-
force:true
|
816
|
-
})
|
817
|
-
.then(function (result) {
|
818
|
-
//console.log(result.updated, result.error ? 'error code: '+result.error.code : 'no error')
|
819
|
-
plugin._wasUpdated = result.updated;
|
820
|
-
if (result.error) plugin._updateError = result.error.code;
|
821
|
-
if (isInteractive) renderUpdateProgress();
|
822
|
-
});
|
823
|
-
})
|
824
|
-
|
825
|
-
}
|
826
|
-
|
827
|
-
function createPromptTask(params) {
|
828
|
-
var deferred = Q.defer();
|
829
|
-
var defaultConfig = {
|
830
|
-
name: 'question',
|
831
|
-
onlyRejectOnError: false
|
832
|
-
};
|
833
|
-
var config = _.extend({}, defaultConfig, params);
|
834
|
-
var schema = [ config ];
|
835
|
-
inquirer.prompt(schema).then(confirmation => {
|
836
|
-
if (!config.onlyRejectOnError && !confirmation.question) deferred.reject(new Error('Aborted. Nothing has been updated.'));
|
837
|
-
deferred.resolve(confirmation.question);
|
838
|
-
}).catch(err => deferred.reject(err));
|
839
|
-
return deferred.promise;
|
840
|
-
}
|
841
|
-
|
842
|
-
function renderUpdateProgress() {
|
843
|
-
var list = plugins.filter(function(plugin) {return !plugin._isMissingAtRepo && plugin._shouldBeUpdated});
|
844
|
-
var settled = plugins.filter(function(plugin) {return _.isBoolean(plugin._wasUpdated);}).length;
|
845
|
-
var total = list.length;
|
846
|
-
//console.log('progress', settled, total);
|
847
|
-
readline.cursorTo(process.stderr, 0);
|
848
|
-
process.stderr.write(chalk.bold.cyan('<info>')+' Updates '+Math.round(100*settled/total)+'% complete');
|
849
|
-
}
|
850
|
-
|
851
|
-
function renderUpdateProgressFinished() {
|
852
|
-
process.stderr.write('\n');
|
853
|
-
}
|
854
|
-
|
855
|
-
function debug() {
|
856
|
-
if (isDebuggingEnabled) {
|
857
|
-
logger.debug.apply(logger, arguments);
|
858
|
-
}
|
859
|
-
}
|
860
|
-
|
861
|
-
function reportFailure(renderer, done) {
|
862
|
-
return function (err) {
|
863
|
-
renderer.log(chalk.redBright(err.message));
|
864
|
-
done(err);
|
865
|
-
};
|
866
|
-
}
|
867
|
-
};
|
1
|
+
import { update as pluginsUpdate } from '../integration/PluginManagement.js'
|
2
|
+
|
3
|
+
export default async function update (logger, ...args) {
|
4
|
+
/** strip flags */
|
5
|
+
const isDryRun = args.includes('--dry-run') || args.includes('--check')
|
6
|
+
const plugins = args.filter(arg => !String(arg).startsWith('--'))
|
7
|
+
await pluginsUpdate({
|
8
|
+
logger,
|
9
|
+
plugins,
|
10
|
+
isDryRun
|
11
|
+
})
|
12
|
+
}
|