adapt-cli 3.0.0 → 3.0.7

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