adapt-cli 3.0.2 → 3.0.6

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.
Files changed (78) hide show
  1. package/.bowerrc +2 -2
  2. package/.eslintignore +1 -1
  3. package/.eslintrc.json +14 -14
  4. package/.github/CONTRIBUTING.md +8 -0
  5. package/.github/ISSUE_TEMPLATE.md +17 -0
  6. package/.github/pull_request_template.md +25 -0
  7. package/.github/workflows/addtomainproject.yml +19 -0
  8. package/.github/workflows/releases.yml +25 -0
  9. package/.travis.yml +46 -46
  10. package/README.md +266 -266
  11. package/bin/adapt.js +3 -3
  12. package/json/help-create/component.json +9 -9
  13. package/json/help-create/course.json +9 -9
  14. package/json/help-create/question.json +9 -9
  15. package/json/help-create.json +12 -12
  16. package/json/help-devinstall.json +9 -9
  17. package/json/help-install.json +10 -10
  18. package/json/help-ls.json +7 -7
  19. package/json/help-register.json +7 -7
  20. package/json/help-rename.json +7 -7
  21. package/json/help-search.json +8 -8
  22. package/json/help-uninstall.json +7 -7
  23. package/json/help-unregister.json +8 -8
  24. package/json/help-update.json +12 -12
  25. package/json/help-version.json +7 -7
  26. package/json/help.json +19 -19
  27. package/lib/api.js +260 -260
  28. package/lib/cli.js +69 -69
  29. package/lib/commands/authenticate.js +18 -18
  30. package/lib/commands/create/component.js +64 -64
  31. package/lib/commands/create/course.js +26 -26
  32. package/lib/commands/create/question.js +18 -18
  33. package/lib/commands/create.js +94 -85
  34. package/lib/commands/devinstall.js +35 -35
  35. package/lib/commands/help.js +31 -31
  36. package/lib/commands/install.js +16 -16
  37. package/lib/commands/ls.js +9 -9
  38. package/lib/commands/register.js +11 -11
  39. package/lib/commands/rename.js +14 -14
  40. package/lib/commands/search.js +11 -11
  41. package/lib/commands/uninstall.js +9 -9
  42. package/lib/commands/unregister.js +12 -12
  43. package/lib/commands/update.js +12 -12
  44. package/lib/commands/version.js +13 -13
  45. package/lib/integration/AdaptFramework/build.js +42 -42
  46. package/lib/integration/AdaptFramework/clone.js +27 -27
  47. package/lib/integration/AdaptFramework/deleteSrcCore.js +9 -9
  48. package/lib/integration/AdaptFramework/deleteSrcCourse.js +9 -9
  49. package/lib/integration/AdaptFramework/download.js +21 -21
  50. package/lib/integration/AdaptFramework/erase.js +34 -34
  51. package/lib/integration/AdaptFramework/getLatestVersion.js +79 -79
  52. package/lib/integration/AdaptFramework/npmInstall.js +21 -21
  53. package/lib/integration/AdaptFramework.js +19 -19
  54. package/lib/integration/Plugin.js +404 -404
  55. package/lib/integration/PluginManagement/autenticate.js +56 -56
  56. package/lib/integration/PluginManagement/install.js +224 -224
  57. package/lib/integration/PluginManagement/print.js +52 -52
  58. package/lib/integration/PluginManagement/register.js +130 -130
  59. package/lib/integration/PluginManagement/rename.js +101 -101
  60. package/lib/integration/PluginManagement/schemas.js +8 -8
  61. package/lib/integration/PluginManagement/search.js +46 -46
  62. package/lib/integration/PluginManagement/uninstall.js +141 -141
  63. package/lib/integration/PluginManagement/unregister.js +101 -101
  64. package/lib/integration/PluginManagement/update.js +224 -224
  65. package/lib/integration/PluginManagement.js +21 -21
  66. package/lib/integration/Project.js +146 -146
  67. package/lib/integration/Target.js +299 -299
  68. package/lib/integration/getBowerRegistryConfig.js +34 -34
  69. package/lib/logger.js +28 -28
  70. package/lib/util/JSONReadValidate.js +34 -34
  71. package/lib/util/constants.js +38 -38
  72. package/lib/util/createPromptTask.js +7 -7
  73. package/lib/util/download.js +45 -45
  74. package/lib/util/errors.js +58 -58
  75. package/lib/util/extract.js +24 -24
  76. package/lib/util/getDirNameFromImportMeta.js +6 -6
  77. package/lib/util/promises.js +36 -36
  78. package/package.json +74 -40
@@ -1,404 +1,404 @@
1
- import slug from 'speakingurl'
2
- import globs from 'globs'
3
- import bower from 'bower'
4
- import endpointParser from 'bower-endpoint-parser'
5
- import semver from 'semver'
6
- import fs from 'fs-extra'
7
- import path from 'path'
8
- import getBowerRegistryConfig from './getBowerRegistryConfig.js'
9
- import { ADAPT_ALLOW_PRERELEASE, PLUGIN_TYPES, PLUGIN_TYPE_FOLDERS, PLUGIN_DEFAULT_TYPE } from '../util/constants.js'
10
- /** @typedef {import("./Project.js").default} Project */
11
- const semverOptions = { includePrerelease: ADAPT_ALLOW_PRERELEASE }
12
-
13
- // when a bower command errors this is the maximum number of attempts the command will be repeated
14
- const BOWER_MAX_TRY = 5
15
-
16
- export default class Plugin {
17
- /**
18
- * @param {Object} options
19
- * @param {string} options.name
20
- * @param {string} options.requestedVersion
21
- * @param {boolean} options.isContrib
22
- * @param {boolean} options.isCompatibleEnabled whether to target the latest compatible version for all plugin installations (overrides requestedVersion)
23
- * @param {Project} options.project
24
- * @param {string} options.cwd
25
- * @param {Object} options.logger
26
- */
27
- constructor ({
28
- name,
29
- requestedVersion = '*',
30
- isContrib = false,
31
- isCompatibleEnabled = false,
32
- project,
33
- cwd = (project?.cwd ?? process.cwd()),
34
- logger
35
- } = {}) {
36
- this.logger = logger
37
- /** @type {Project} */
38
- this.project = project
39
- this.cwd = cwd
40
- this.BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd: this.cwd })
41
- const endpoint = name + '#' + (isCompatibleEnabled ? '*' : requestedVersion)
42
- const ep = endpointParser.decompose(endpoint)
43
- this.sourcePath = null
44
- this.name = ep.name || ep.source
45
- this.packageName = (/^adapt-/i.test(this.name) ? '' : 'adapt-') + (!isContrib ? '' : 'contrib-') + slug(this.name, { maintainCase: true })
46
- // the constraint given by the user
47
- this.requestedVersion = requestedVersion
48
- // the most recent version of the plugin compatible with the given framework
49
- this.latestCompatibleSourceVersion = null
50
- // a non-wildcard constraint resolved to the highest version of the plugin that satisfies the requestedVersion and is compatible with the framework
51
- this.matchedVersion = null
52
- // a flag describing if the plugin can be updated
53
- this.canBeUpdated = null
54
-
55
- const isNameAPath = /\\|\//g.test(this.name)
56
- const isVersionAPath = /\\|\//g.test(this.requestedVersion)
57
- const isLocalPath = (isNameAPath || isVersionAPath)
58
- if (isLocalPath) {
59
- // wait to name the plugin until the local config file is loaded
60
- this.sourcePath = isNameAPath ? this.name : this.requestedVersion
61
- this.name = isVersionAPath ? this.packageName : ''
62
- this.packageName = isNameAPath ? '' : this.packageName
63
- this.requestedVersion = '*'
64
- }
65
- // the path of the source files
66
- this.projectPath = null
67
- // the project plugin .bower.json or bower.json
68
- this._projectInfo = null
69
- // the result of a query to the server or disk for source files
70
- this._sourceInfo = null
71
- // server given versions
72
- this._versionsInfo = null
73
-
74
- Plugin.instances.push(this)
75
- }
76
-
77
- /**
78
- * the installed version is the latest version
79
- * @returns {boolean|null}
80
- */
81
- get isUpToDate () {
82
- if (!this.hasFrameworkCompatibleVersion) return true;
83
- const canCheckSourceAgainstProject = (this.latestSourceVersion && this.projectVersion)
84
- if (!canCheckSourceAgainstProject) return null
85
- const isLatestVersion = (this.projectVersion === this.latestSourceVersion)
86
- const isLatestMatchedVersion = (this.projectVersion === this.matchedVersion)
87
- const isProjectVersionGreater = semver.gt(this.projectVersion, this.matchedVersion)
88
- return (isLatestVersion || isLatestMatchedVersion || isProjectVersionGreater)
89
- }
90
-
91
- /**
92
- * the most recent version of the plugin
93
- * @returns {string|null}
94
- */
95
- get latestSourceVersion () {
96
- return (this._sourceInfo?.version || null)
97
- }
98
-
99
- /**
100
- * the installed version of the plugin
101
- * @returns {string|null}
102
- */
103
- get projectVersion () {
104
- return (this._projectInfo?.version || null)
105
- }
106
-
107
- /**
108
- * a list of tags denoting the source versions of the plugin
109
- * @returns {[string]}
110
- */
111
- get sourceVersions () {
112
- return this._versionsInfo
113
- }
114
-
115
- /**
116
- * plugin will be or was installed from a local source
117
- * @returns {boolean}
118
- */
119
- get isLocalSource () {
120
- return Boolean(this.sourcePath || this?._projectInfo?._wasInstalledFromPath)
121
- }
122
-
123
- /**
124
- * check if source path is a zip
125
- * @returns {boolean}
126
- */
127
- get isLocalSourceZip () {
128
- return Boolean(this.isLocalSource && (this.sourcePath?.includes('.zip') || this._projectInfo?._source?.includes('.zip')))
129
- }
130
-
131
- /** @returns {boolean} */
132
- get isVersioned () {
133
- return Boolean(this.sourceVersions?.length)
134
- }
135
-
136
- /**
137
- * is a contrib plugin
138
- * @returns {boolean}
139
- */
140
- get isContrib () {
141
- return /^adapt-contrib/.test(this.packageName)
142
- }
143
-
144
- /**
145
- * whether querying the server or disk for plugin information worked
146
- * @returns {boolean}
147
- */
148
- get isPresent () {
149
- return Boolean(this._projectInfo || this._sourceInfo)
150
- }
151
-
152
- /**
153
- * has user requested version
154
- * @returns {boolean}
155
- */
156
- get hasUserRequestVersion () {
157
- return (this.requestedVersion !== '*')
158
- }
159
-
160
- /**
161
- * the supplied a constraint is valid and supported by the plugin
162
- * @returns {boolean|null}
163
- */
164
- get hasValidRequestVersion () {
165
- return (this.latestSourceVersion)
166
- ? semver.validRange(this.requestedVersion, semverOptions) &&
167
- (this.isVersioned
168
- ? semver.maxSatisfying(this.sourceVersions, this.requestedVersion, semverOptions) !== null
169
- : semver.satisfies(this.latestSourceVersion, this.requestedVersion)
170
- )
171
- : null
172
- }
173
-
174
- /** @returns {boolean} */
175
- get hasFrameworkCompatibleVersion () {
176
- return (this.latestCompatibleSourceVersion !== null)
177
- }
178
-
179
- async fetchSourceInfo () {
180
- if (this.isLocalSource) return await this.fetchLocalSourceInfo()
181
- await this.fetchBowerInfo()
182
- }
183
-
184
- async fetchLocalSourceInfo () {
185
- if (this._sourceInfo) return this._sourceInfo
186
- this._sourceInfo = null
187
- if (!this.isLocalSource) throw new Error('Plugin name or version must be a path to the source')
188
- if (this.isLocalSourceZip) throw new Error('Cannot install from zip files')
189
- this._sourceInfo = await new Promise((resolve, reject) => {
190
- // get bower.json data
191
- const paths = [
192
- path.resolve(this.cwd, `${this.sourcePath}/bower.json`)
193
- ]
194
- const bowerJSON = paths.reduce((bowerJSON, bowerJSONPath) => {
195
- if (bowerJSON) return bowerJSON
196
- if (!fs.existsSync(bowerJSONPath)) return null
197
- return fs.readJSONSync(bowerJSONPath)
198
- }, null)
199
- resolve(bowerJSON)
200
- })
201
- if (!this._sourceInfo) return
202
- this.name = this._sourceInfo.name
203
- this.matchedVersion = this.latestSourceVersion
204
- this.packageName = this.name
205
- }
206
-
207
- async fetchBowerInfo () {
208
- if (this._sourceInfo) return this._sourceInfo
209
- this._sourceInfo = null
210
- if (this.isLocalSource) return
211
- const perform = async (attemptCount = 0) => {
212
- try {
213
- return await new Promise((resolve, reject) => {
214
- bower.commands.info(`${this.packageName}`, null, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
215
- .on('end', resolve)
216
- .on('error', reject)
217
- })
218
- } catch (err) {
219
- const isFinished = (err?.code === 'ENOTFOUND' || attemptCount >= BOWER_MAX_TRY)
220
- if (isFinished) return null
221
- return await perform(attemptCount++)
222
- }
223
- }
224
- const info = await perform()
225
- if (!info) return
226
- this._sourceInfo = info.latest
227
- this._versionsInfo = info.versions.filter(version => semverOptions.includePrerelease ? true : !semver.prerelease(version))
228
- }
229
-
230
- async fetchProjectInfo () {
231
- if (this._projectInfo) return this._projectInfo
232
- this._projectInfo = null
233
- this._projectInfo = await new Promise((resolve, reject) => {
234
- // get bower.json data
235
- globs([
236
- `${this.cwd.replace(/\\/g, '/')}/src/*/${this.packageName}/.bower.json`,
237
- `${this.cwd.replace(/\\/g, '/')}/src/*/${this.packageName}/bower.json`
238
- ], (err, matches) => {
239
- if (err) return resolve(null)
240
- const tester = new RegExp(`/${this.packageName}/`, 'i')
241
- const match = matches.find(match => tester.test(match))
242
- if (!match) {
243
- // widen the search
244
- globs([
245
- `${this.cwd.replace(/\\/g, '/')}/src/**/.bower.json`,
246
- `${this.cwd.replace(/\\/g, '/')}/src/**/bower.json`
247
- ], (err, matches) => {
248
- if (err) return resolve(null)
249
- const tester = new RegExp(`/${this.packageName}/`, 'i')
250
- const match = matches.find(match => tester.test(match))
251
- if (!match) return resolve(null)
252
- this.projectPath = path.resolve(match, '../')
253
- resolve(fs.readJSONSync(match))
254
- })
255
- return
256
- }
257
- this.projectPath = path.resolve(match, '../')
258
- resolve(fs.readJSONSync(match))
259
- })
260
- })
261
- if (!this._projectInfo) return
262
- this.name = this._projectInfo.name
263
- this.packageName = this.name
264
- }
265
-
266
- async findCompatibleVersion (framework) {
267
- const getBowerVersionInfo = async (version) => {
268
- const perform = async (attemptCount = 0) => {
269
- try {
270
- return await new Promise((resolve, reject) => {
271
- bower.commands.info(`${this.packageName}@${version}`, null, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
272
- .on('end', resolve)
273
- .on('error', reject)
274
- })
275
- } catch (err) {
276
- const isFinished = (err?.code === 'ENOTFOUND' || attemptCount >= BOWER_MAX_TRY)
277
- if (isFinished) return null
278
- return await perform(attemptCount++)
279
- }
280
- }
281
- return await perform()
282
- }
283
- const getMatchingVersion = async () => {
284
- if (this.isLocalSource) {
285
- const info = this.projectVersion ? this._projectInfo : this._sourceInfo
286
- const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(info.version, this.requestedVersion, semverOptions)
287
- const satisfiesFramework = semver.satisfies(framework, info.framework)
288
- if (satisfiesFramework && satisfiesConstraint) this.latestCompatibleSourceVersion = info.version
289
- return info.version
290
- }
291
-
292
- if (!this.isPresent) return null
293
-
294
- // check if the latest version is compatible
295
- const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(this._sourceInfo.version, this.requestedVersion, semverOptions)
296
- const satisfiesFramework = semver.satisfies(framework, this._sourceInfo.framework, semverOptions)
297
- if (!this.latestCompatibleSourceVersion && satisfiesFramework) this.latestCompatibleSourceVersion = this.latestSourceVersion
298
- if (satisfiesConstraint && satisfiesFramework) {
299
- return this.latestSourceVersion
300
- }
301
-
302
- if (!this.isVersioned) return null
303
-
304
- // find the highest version that is compatible with the framework and constraint
305
- const searchVersionInfo = async (framework, versionIndex = 0) => {
306
- const versioninfo = await getBowerVersionInfo(this.sourceVersions[versionIndex])
307
- // give up if there is any failure to obtain version info
308
- if (!this.isPresent) return null
309
- // check that the proposed plugin is compatible with the contraint and installed framework
310
- const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(versioninfo.version, this.requestedVersion, semverOptions)
311
- const satisfiesFramework = semver.satisfies(framework, versioninfo.framework, semverOptions)
312
- if (!this.latestCompatibleSourceVersion && satisfiesFramework) this.latestCompatibleSourceVersion = versioninfo.version
313
- const checkNext = (!satisfiesFramework || !satisfiesConstraint)
314
- const hasNoMoreVersions = (versionIndex + 1 >= this.sourceVersions.length)
315
- if (checkNext && hasNoMoreVersions) return null
316
- if (checkNext) return await searchVersionInfo(framework, versionIndex + 1)
317
- return versioninfo.version
318
- }
319
- return await searchVersionInfo(framework)
320
- }
321
- this.matchedVersion = await getMatchingVersion()
322
- this.canBeUpdated = (this.projectVersion && this.matchedVersion) && (this.projectVersion !== this.matchedVersion)
323
- }
324
-
325
- /**
326
- * @returns {string}
327
- */
328
- async getType () {
329
- if (this._type) return this._type
330
- const info = await this.getInfo()
331
- const foundAttributeType = PLUGIN_TYPES.find(type => info[type])
332
- const foundKeywordType = info.keywords
333
- .map(keyword => {
334
- const typematches = PLUGIN_TYPES.filter(type => keyword?.toLowerCase()?.includes(type))
335
- return typematches.length ? typematches[0] : null
336
- })
337
- .filter(Boolean)[0]
338
- return (this._type = foundAttributeType || foundKeywordType || PLUGIN_DEFAULT_TYPE)
339
- }
340
-
341
- async getTypeFolder () {
342
- const type = await this.getType()
343
- return PLUGIN_TYPE_FOLDERS[type]
344
- }
345
-
346
- async getInfo () {
347
- if (this._projectInfo) return this._projectInfo
348
- if (!this._sourceInfo) await this.fetchSourceInfo()
349
- return this._sourceInfo
350
- }
351
-
352
- async getRepositoryUrl () {
353
- if (this._repositoryUrl) return this._repositoryUrl
354
- if (this.isLocalSource) return
355
- const url = await new Promise((resolve, reject) => {
356
- bower.commands.lookup(this.packageName, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
357
- .on('end', resolve)
358
- .on('error', reject)
359
- })
360
- return (this._repositoryUrl = url)
361
- }
362
-
363
- /** @returns {string} */
364
- toString () {
365
- const isAny = (this.projectVersion === '*' || this.projectVersion === null)
366
- return `${this.packageName}${isAny ? '' : `@${this.projectVersion}`}`
367
- }
368
-
369
- async getSchemaPaths () {
370
- if (this.isLocalSource) await this.fetchLocalSourceInfo()
371
- else if (this.project) await this.fetchProjectInfo()
372
- else throw new Error(`Cannot fetch schemas from remote plugin: ${this.name}`)
373
- const pluginPath = this.projectPath ?? this.sourcePath
374
- return new Promise((resolve, reject) => {
375
- return globs(path.resolve(this.cwd, pluginPath, '**/*.schema.json'), (err, matches) => {
376
- if (err) return reject(err)
377
- resolve(matches)
378
- })
379
- })
380
- }
381
-
382
- /**
383
- * Read plugin data from pluginPath
384
- * @param {Object} options
385
- * @param {string} options.pluginPath Path to source directory
386
- * @param {string} [options.projectPath=process.cwd()] Optional path to potential installation project
387
- * @returns {Plugin}
388
- */
389
- static async fromPath ({
390
- pluginPath,
391
- projectPath = process.cwd()
392
- }) {
393
- const plugin = new Plugin({
394
- name: pluginPath,
395
- cwd: projectPath
396
- })
397
- await plugin.fetchLocalSourceInfo()
398
- return plugin
399
- }
400
-
401
- static get instances () {
402
- return (Plugin._instances = Plugin._instances || [])
403
- }
404
- }
1
+ import slug from 'speakingurl'
2
+ import globs from 'globs'
3
+ import bower from 'bower'
4
+ import endpointParser from 'bower-endpoint-parser'
5
+ import semver from 'semver'
6
+ import fs from 'fs-extra'
7
+ import path from 'path'
8
+ import getBowerRegistryConfig from './getBowerRegistryConfig.js'
9
+ import { ADAPT_ALLOW_PRERELEASE, PLUGIN_TYPES, PLUGIN_TYPE_FOLDERS, PLUGIN_DEFAULT_TYPE } from '../util/constants.js'
10
+ /** @typedef {import("./Project.js").default} Project */
11
+ const semverOptions = { includePrerelease: ADAPT_ALLOW_PRERELEASE }
12
+
13
+ // when a bower command errors this is the maximum number of attempts the command will be repeated
14
+ const BOWER_MAX_TRY = 5
15
+
16
+ export default class Plugin {
17
+ /**
18
+ * @param {Object} options
19
+ * @param {string} options.name
20
+ * @param {string} options.requestedVersion
21
+ * @param {boolean} options.isContrib
22
+ * @param {boolean} options.isCompatibleEnabled whether to target the latest compatible version for all plugin installations (overrides requestedVersion)
23
+ * @param {Project} options.project
24
+ * @param {string} options.cwd
25
+ * @param {Object} options.logger
26
+ */
27
+ constructor ({
28
+ name,
29
+ requestedVersion = '*',
30
+ isContrib = false,
31
+ isCompatibleEnabled = false,
32
+ project,
33
+ cwd = (project?.cwd ?? process.cwd()),
34
+ logger
35
+ } = {}) {
36
+ this.logger = logger
37
+ /** @type {Project} */
38
+ this.project = project
39
+ this.cwd = cwd
40
+ this.BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd: this.cwd })
41
+ const endpoint = name + '#' + (isCompatibleEnabled ? '*' : requestedVersion)
42
+ const ep = endpointParser.decompose(endpoint)
43
+ this.sourcePath = null
44
+ this.name = ep.name || ep.source
45
+ this.packageName = (/^adapt-/i.test(this.name) ? '' : 'adapt-') + (!isContrib ? '' : 'contrib-') + slug(this.name, { maintainCase: true })
46
+ // the constraint given by the user
47
+ this.requestedVersion = requestedVersion
48
+ // the most recent version of the plugin compatible with the given framework
49
+ this.latestCompatibleSourceVersion = null
50
+ // a non-wildcard constraint resolved to the highest version of the plugin that satisfies the requestedVersion and is compatible with the framework
51
+ this.matchedVersion = null
52
+ // a flag describing if the plugin can be updated
53
+ this.canBeUpdated = null
54
+
55
+ const isNameAPath = /\\|\//g.test(this.name)
56
+ const isVersionAPath = /\\|\//g.test(this.requestedVersion)
57
+ const isLocalPath = (isNameAPath || isVersionAPath)
58
+ if (isLocalPath) {
59
+ // wait to name the plugin until the local config file is loaded
60
+ this.sourcePath = isNameAPath ? this.name : this.requestedVersion
61
+ this.name = isVersionAPath ? this.packageName : ''
62
+ this.packageName = isNameAPath ? '' : this.packageName
63
+ this.requestedVersion = '*'
64
+ }
65
+ // the path of the source files
66
+ this.projectPath = null
67
+ // the project plugin .bower.json or bower.json
68
+ this._projectInfo = null
69
+ // the result of a query to the server or disk for source files
70
+ this._sourceInfo = null
71
+ // server given versions
72
+ this._versionsInfo = null
73
+
74
+ Plugin.instances.push(this)
75
+ }
76
+
77
+ /**
78
+ * the installed version is the latest version
79
+ * @returns {boolean|null}
80
+ */
81
+ get isUpToDate () {
82
+ if (!this.hasFrameworkCompatibleVersion) return true;
83
+ const canCheckSourceAgainstProject = (this.latestSourceVersion && this.projectVersion)
84
+ if (!canCheckSourceAgainstProject) return null
85
+ const isLatestVersion = (this.projectVersion === this.latestSourceVersion)
86
+ const isLatestMatchedVersion = (this.projectVersion === this.matchedVersion)
87
+ const isProjectVersionGreater = semver.gt(this.projectVersion, this.matchedVersion)
88
+ return (isLatestVersion || isLatestMatchedVersion || isProjectVersionGreater)
89
+ }
90
+
91
+ /**
92
+ * the most recent version of the plugin
93
+ * @returns {string|null}
94
+ */
95
+ get latestSourceVersion () {
96
+ return (this._sourceInfo?.version || null)
97
+ }
98
+
99
+ /**
100
+ * the installed version of the plugin
101
+ * @returns {string|null}
102
+ */
103
+ get projectVersion () {
104
+ return (this._projectInfo?.version || null)
105
+ }
106
+
107
+ /**
108
+ * a list of tags denoting the source versions of the plugin
109
+ * @returns {[string]}
110
+ */
111
+ get sourceVersions () {
112
+ return this._versionsInfo
113
+ }
114
+
115
+ /**
116
+ * plugin will be or was installed from a local source
117
+ * @returns {boolean}
118
+ */
119
+ get isLocalSource () {
120
+ return Boolean(this.sourcePath || this?._projectInfo?._wasInstalledFromPath)
121
+ }
122
+
123
+ /**
124
+ * check if source path is a zip
125
+ * @returns {boolean}
126
+ */
127
+ get isLocalSourceZip () {
128
+ return Boolean(this.isLocalSource && (this.sourcePath?.includes('.zip') || this._projectInfo?._source?.includes('.zip')))
129
+ }
130
+
131
+ /** @returns {boolean} */
132
+ get isVersioned () {
133
+ return Boolean(this.sourceVersions?.length)
134
+ }
135
+
136
+ /**
137
+ * is a contrib plugin
138
+ * @returns {boolean}
139
+ */
140
+ get isContrib () {
141
+ return /^adapt-contrib/.test(this.packageName)
142
+ }
143
+
144
+ /**
145
+ * whether querying the server or disk for plugin information worked
146
+ * @returns {boolean}
147
+ */
148
+ get isPresent () {
149
+ return Boolean(this._projectInfo || this._sourceInfo)
150
+ }
151
+
152
+ /**
153
+ * has user requested version
154
+ * @returns {boolean}
155
+ */
156
+ get hasUserRequestVersion () {
157
+ return (this.requestedVersion !== '*')
158
+ }
159
+
160
+ /**
161
+ * the supplied a constraint is valid and supported by the plugin
162
+ * @returns {boolean|null}
163
+ */
164
+ get hasValidRequestVersion () {
165
+ return (this.latestSourceVersion)
166
+ ? semver.validRange(this.requestedVersion, semverOptions) &&
167
+ (this.isVersioned
168
+ ? semver.maxSatisfying(this.sourceVersions, this.requestedVersion, semverOptions) !== null
169
+ : semver.satisfies(this.latestSourceVersion, this.requestedVersion)
170
+ )
171
+ : null
172
+ }
173
+
174
+ /** @returns {boolean} */
175
+ get hasFrameworkCompatibleVersion () {
176
+ return (this.latestCompatibleSourceVersion !== null)
177
+ }
178
+
179
+ async fetchSourceInfo () {
180
+ if (this.isLocalSource) return await this.fetchLocalSourceInfo()
181
+ await this.fetchBowerInfo()
182
+ }
183
+
184
+ async fetchLocalSourceInfo () {
185
+ if (this._sourceInfo) return this._sourceInfo
186
+ this._sourceInfo = null
187
+ if (!this.isLocalSource) throw new Error('Plugin name or version must be a path to the source')
188
+ if (this.isLocalSourceZip) throw new Error('Cannot install from zip files')
189
+ this._sourceInfo = await new Promise((resolve, reject) => {
190
+ // get bower.json data
191
+ const paths = [
192
+ path.resolve(this.cwd, `${this.sourcePath}/bower.json`)
193
+ ]
194
+ const bowerJSON = paths.reduce((bowerJSON, bowerJSONPath) => {
195
+ if (bowerJSON) return bowerJSON
196
+ if (!fs.existsSync(bowerJSONPath)) return null
197
+ return fs.readJSONSync(bowerJSONPath)
198
+ }, null)
199
+ resolve(bowerJSON)
200
+ })
201
+ if (!this._sourceInfo) return
202
+ this.name = this._sourceInfo.name
203
+ this.matchedVersion = this.latestSourceVersion
204
+ this.packageName = this.name
205
+ }
206
+
207
+ async fetchBowerInfo () {
208
+ if (this._sourceInfo) return this._sourceInfo
209
+ this._sourceInfo = null
210
+ if (this.isLocalSource) return
211
+ const perform = async (attemptCount = 0) => {
212
+ try {
213
+ return await new Promise((resolve, reject) => {
214
+ bower.commands.info(`${this.packageName}`, null, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
215
+ .on('end', resolve)
216
+ .on('error', reject)
217
+ })
218
+ } catch (err) {
219
+ const isFinished = (err?.code === 'ENOTFOUND' || attemptCount >= BOWER_MAX_TRY)
220
+ if (isFinished) return null
221
+ return await perform(attemptCount++)
222
+ }
223
+ }
224
+ const info = await perform()
225
+ if (!info) return
226
+ this._sourceInfo = info.latest
227
+ this._versionsInfo = info.versions.filter(version => semverOptions.includePrerelease ? true : !semver.prerelease(version))
228
+ }
229
+
230
+ async fetchProjectInfo () {
231
+ if (this._projectInfo) return this._projectInfo
232
+ this._projectInfo = null
233
+ this._projectInfo = await new Promise((resolve, reject) => {
234
+ // get bower.json data
235
+ globs([
236
+ `${this.cwd.replace(/\\/g, '/')}/src/*/${this.packageName}/.bower.json`,
237
+ `${this.cwd.replace(/\\/g, '/')}/src/*/${this.packageName}/bower.json`
238
+ ], (err, matches) => {
239
+ if (err) return resolve(null)
240
+ const tester = new RegExp(`/${this.packageName}/`, 'i')
241
+ const match = matches.find(match => tester.test(match))
242
+ if (!match) {
243
+ // widen the search
244
+ globs([
245
+ `${this.cwd.replace(/\\/g, '/')}/src/**/.bower.json`,
246
+ `${this.cwd.replace(/\\/g, '/')}/src/**/bower.json`
247
+ ], (err, matches) => {
248
+ if (err) return resolve(null)
249
+ const tester = new RegExp(`/${this.packageName}/`, 'i')
250
+ const match = matches.find(match => tester.test(match))
251
+ if (!match) return resolve(null)
252
+ this.projectPath = path.resolve(match, '../')
253
+ resolve(fs.readJSONSync(match))
254
+ })
255
+ return
256
+ }
257
+ this.projectPath = path.resolve(match, '../')
258
+ resolve(fs.readJSONSync(match))
259
+ })
260
+ })
261
+ if (!this._projectInfo) return
262
+ this.name = this._projectInfo.name
263
+ this.packageName = this.name
264
+ }
265
+
266
+ async findCompatibleVersion (framework) {
267
+ const getBowerVersionInfo = async (version) => {
268
+ const perform = async (attemptCount = 0) => {
269
+ try {
270
+ return await new Promise((resolve, reject) => {
271
+ bower.commands.info(`${this.packageName}@${version}`, null, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
272
+ .on('end', resolve)
273
+ .on('error', reject)
274
+ })
275
+ } catch (err) {
276
+ const isFinished = (err?.code === 'ENOTFOUND' || attemptCount >= BOWER_MAX_TRY)
277
+ if (isFinished) return null
278
+ return await perform(attemptCount++)
279
+ }
280
+ }
281
+ return await perform()
282
+ }
283
+ const getMatchingVersion = async () => {
284
+ if (this.isLocalSource) {
285
+ const info = this.projectVersion ? this._projectInfo : this._sourceInfo
286
+ const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(info.version, this.requestedVersion, semverOptions)
287
+ const satisfiesFramework = semver.satisfies(framework, info.framework)
288
+ if (satisfiesFramework && satisfiesConstraint) this.latestCompatibleSourceVersion = info.version
289
+ return info.version
290
+ }
291
+
292
+ if (!this.isPresent) return null
293
+
294
+ // check if the latest version is compatible
295
+ const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(this._sourceInfo.version, this.requestedVersion, semverOptions)
296
+ const satisfiesFramework = semver.satisfies(framework, this._sourceInfo.framework, semverOptions)
297
+ if (!this.latestCompatibleSourceVersion && satisfiesFramework) this.latestCompatibleSourceVersion = this.latestSourceVersion
298
+ if (satisfiesConstraint && satisfiesFramework) {
299
+ return this.latestSourceVersion
300
+ }
301
+
302
+ if (!this.isVersioned) return null
303
+
304
+ // find the highest version that is compatible with the framework and constraint
305
+ const searchVersionInfo = async (framework, versionIndex = 0) => {
306
+ const versioninfo = await getBowerVersionInfo(this.sourceVersions[versionIndex])
307
+ // give up if there is any failure to obtain version info
308
+ if (!this.isPresent) return null
309
+ // check that the proposed plugin is compatible with the contraint and installed framework
310
+ const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(versioninfo.version, this.requestedVersion, semverOptions)
311
+ const satisfiesFramework = semver.satisfies(framework, versioninfo.framework, semverOptions)
312
+ if (!this.latestCompatibleSourceVersion && satisfiesFramework) this.latestCompatibleSourceVersion = versioninfo.version
313
+ const checkNext = (!satisfiesFramework || !satisfiesConstraint)
314
+ const hasNoMoreVersions = (versionIndex + 1 >= this.sourceVersions.length)
315
+ if (checkNext && hasNoMoreVersions) return null
316
+ if (checkNext) return await searchVersionInfo(framework, versionIndex + 1)
317
+ return versioninfo.version
318
+ }
319
+ return await searchVersionInfo(framework)
320
+ }
321
+ this.matchedVersion = await getMatchingVersion()
322
+ this.canBeUpdated = (this.projectVersion && this.matchedVersion) && (this.projectVersion !== this.matchedVersion)
323
+ }
324
+
325
+ /**
326
+ * @returns {string}
327
+ */
328
+ async getType () {
329
+ if (this._type) return this._type
330
+ const info = await this.getInfo()
331
+ const foundAttributeType = PLUGIN_TYPES.find(type => info[type])
332
+ const foundKeywordType = info.keywords
333
+ .map(keyword => {
334
+ const typematches = PLUGIN_TYPES.filter(type => keyword?.toLowerCase()?.includes(type))
335
+ return typematches.length ? typematches[0] : null
336
+ })
337
+ .filter(Boolean)[0]
338
+ return (this._type = foundAttributeType || foundKeywordType || PLUGIN_DEFAULT_TYPE)
339
+ }
340
+
341
+ async getTypeFolder () {
342
+ const type = await this.getType()
343
+ return PLUGIN_TYPE_FOLDERS[type]
344
+ }
345
+
346
+ async getInfo () {
347
+ if (this._projectInfo) return this._projectInfo
348
+ if (!this._sourceInfo) await this.fetchSourceInfo()
349
+ return this._sourceInfo
350
+ }
351
+
352
+ async getRepositoryUrl () {
353
+ if (this._repositoryUrl) return this._repositoryUrl
354
+ if (this.isLocalSource) return
355
+ const url = await new Promise((resolve, reject) => {
356
+ bower.commands.lookup(this.packageName, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
357
+ .on('end', resolve)
358
+ .on('error', reject)
359
+ })
360
+ return (this._repositoryUrl = url)
361
+ }
362
+
363
+ /** @returns {string} */
364
+ toString () {
365
+ const isAny = (this.projectVersion === '*' || this.projectVersion === null)
366
+ return `${this.packageName}${isAny ? '' : `@${this.projectVersion}`}`
367
+ }
368
+
369
+ async getSchemaPaths () {
370
+ if (this.isLocalSource) await this.fetchLocalSourceInfo()
371
+ else if (this.project) await this.fetchProjectInfo()
372
+ else throw new Error(`Cannot fetch schemas from remote plugin: ${this.name}`)
373
+ const pluginPath = this.projectPath ?? this.sourcePath
374
+ return new Promise((resolve, reject) => {
375
+ return globs(path.resolve(this.cwd, pluginPath, '**/*.schema.json'), (err, matches) => {
376
+ if (err) return reject(err)
377
+ resolve(matches)
378
+ })
379
+ })
380
+ }
381
+
382
+ /**
383
+ * Read plugin data from pluginPath
384
+ * @param {Object} options
385
+ * @param {string} options.pluginPath Path to source directory
386
+ * @param {string} [options.projectPath=process.cwd()] Optional path to potential installation project
387
+ * @returns {Plugin}
388
+ */
389
+ static async fromPath ({
390
+ pluginPath,
391
+ projectPath = process.cwd()
392
+ }) {
393
+ const plugin = new Plugin({
394
+ name: pluginPath,
395
+ cwd: projectPath
396
+ })
397
+ await plugin.fetchLocalSourceInfo()
398
+ return plugin
399
+ }
400
+
401
+ static get instances () {
402
+ return (Plugin._instances = Plugin._instances || [])
403
+ }
404
+ }