adapt-cli 2.1.11 → 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/.bowerrc +2 -2
- package/.eslintignore +1 -0
- package/.eslintrc.json +14 -0
- package/.travis.yml +46 -46
- package/README.md +266 -266
- package/bin/adapt.js +3 -0
- package/json/help-create/component.json +9 -9
- package/json/help-create/course.json +9 -9
- package/json/help-create/question.json +9 -0
- package/json/help-create.json +12 -11
- package/json/help-devinstall.json +9 -9
- package/json/help-install.json +10 -10
- package/json/help-ls.json +7 -7
- package/json/help-register.json +7 -7
- package/json/help-rename.json +7 -7
- package/json/help-search.json +8 -8
- package/json/help-uninstall.json +7 -7
- package/json/help-unregister.json +8 -8
- package/json/help-update.json +12 -12
- package/json/help-version.json +7 -7
- package/json/help.json +19 -19
- package/lib/api.js +260 -0
- package/lib/cli.js +69 -52
- package/lib/commands/authenticate.js +18 -0
- package/lib/commands/create/component.js +64 -81
- package/lib/commands/create/course.js +26 -87
- package/lib/commands/create/question.js +18 -0
- package/lib/commands/create.js +85 -104
- package/lib/commands/devinstall.js +35 -97
- package/lib/commands/help.js +31 -52
- package/lib/commands/install.js +16 -856
- package/lib/commands/ls.js +9 -24
- package/lib/commands/register.js +11 -201
- package/lib/commands/rename.js +14 -138
- package/lib/commands/search.js +11 -29
- package/lib/commands/uninstall.js +9 -136
- package/lib/commands/unregister.js +12 -105
- package/lib/commands/update.js +12 -889
- package/lib/commands/version.js +13 -15
- 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 +40 -49
- 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 -15
- package/lib/ConsoleRenderer.js +0 -10
- package/lib/Constants.js +0 -69
- 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 -51
- 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 -64
- package/lib/promise/build.js +0 -20
- package/lib/promise/cloneInstall.js +0 -35
- package/lib/promise/confirmBuild.js +0 -7
- 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
@@ -0,0 +1,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
|
+
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
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import chalk from 'chalk'
|
2
|
+
import inquirer from 'inquirer'
|
3
|
+
import request from 'request'
|
4
|
+
import getBowerRegistryConfig from '../getBowerRegistryConfig.js'
|
5
|
+
import path from 'path'
|
6
|
+
|
7
|
+
export default async function authenticate ({
|
8
|
+
pluginName,
|
9
|
+
cwd = process.cwd()
|
10
|
+
} = {}) {
|
11
|
+
cwd = path.resolve(process.cwd(), cwd)
|
12
|
+
const BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd })
|
13
|
+
// check if github, do github device oauth workflow
|
14
|
+
// if not github, send request to repo anyway
|
15
|
+
const questions = [
|
16
|
+
{
|
17
|
+
name: 'username',
|
18
|
+
message: chalk.cyan('GitHub username')
|
19
|
+
},
|
20
|
+
{
|
21
|
+
name: 'token',
|
22
|
+
message: chalk.cyan('GitHub personal access token (with public_repo access)'),
|
23
|
+
type: 'password',
|
24
|
+
mask: '*'
|
25
|
+
}
|
26
|
+
]
|
27
|
+
if (!pluginName) {
|
28
|
+
questions.unshift({
|
29
|
+
name: 'pluginName',
|
30
|
+
message: chalk.cyan('Plugin name'),
|
31
|
+
default: pluginName
|
32
|
+
})
|
33
|
+
}
|
34
|
+
const confirmation = await inquirer.prompt(questions)
|
35
|
+
if (!pluginName) {
|
36
|
+
({ pluginName } = confirmation)
|
37
|
+
}
|
38
|
+
const { username, token } = confirmation
|
39
|
+
return new Promise((resolve, reject) => {
|
40
|
+
request({
|
41
|
+
uri: `${BOWER_REGISTRY_CONFIG.register}authenticate/${username}/${pluginName}?access_token=${token}`,
|
42
|
+
method: 'GET',
|
43
|
+
headers: { 'User-Agent': 'adapt-cli' },
|
44
|
+
followRedirect: false
|
45
|
+
}, (err, res, body) => {
|
46
|
+
if (err) return reject(err)
|
47
|
+
if (res.statusCode !== 200) reject(new Error(`The server responded with ${res.statusCode}`))
|
48
|
+
try {
|
49
|
+
const bodyJSON = JSON.parse(body)
|
50
|
+
resolve({ username, token, pluginName, ...bodyJSON })
|
51
|
+
} catch (err) {
|
52
|
+
reject(err)
|
53
|
+
}
|
54
|
+
})
|
55
|
+
})
|
56
|
+
}
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import chalk from 'chalk'
|
2
|
+
import { eachOfSeries } from 'async'
|
3
|
+
import { createPromptTask } from '../../util/createPromptTask.js'
|
4
|
+
import { errorPrinter, packageNamePrinter, versionPrinter } from './print.js'
|
5
|
+
import { eachOfLimitProgress, eachOfSeriesProgress } from '../../util/promises.js'
|
6
|
+
import Project from '../Project.js'
|
7
|
+
import Target from '../Target.js'
|
8
|
+
import bower from 'bower'
|
9
|
+
import { difference } from 'lodash-es'
|
10
|
+
import path from 'path'
|
11
|
+
|
12
|
+
export default async function install ({
|
13
|
+
plugins,
|
14
|
+
dev = false,
|
15
|
+
isInteractive = true,
|
16
|
+
isDryRun = false, // whether to summarise installation without modifying anything
|
17
|
+
isCompatibleEnabled = false,
|
18
|
+
isClean = false,
|
19
|
+
cwd = process.cwd(),
|
20
|
+
logger = null
|
21
|
+
}) {
|
22
|
+
cwd = path.resolve(process.cwd(), cwd)
|
23
|
+
isClean && await new Promise(resolve => bower.commands.cache.clean().on('end', resolve))
|
24
|
+
const project = new Project({ cwd, logger })
|
25
|
+
project.tryThrowInvalidPath()
|
26
|
+
|
27
|
+
logger?.log(chalk.cyan(`${dev ? 'cloning' : 'installing'} adapt dependencies...`))
|
28
|
+
|
29
|
+
const targets = await getInstallTargets({ logger, project, plugins, isCompatibleEnabled })
|
30
|
+
if (!targets?.length) return targets
|
31
|
+
|
32
|
+
await loadPluginData({ logger, project, targets })
|
33
|
+
await conflictResolution({ logger, targets, isInteractive, dev })
|
34
|
+
if (isDryRun) {
|
35
|
+
await summariseDryRun({ logger, targets })
|
36
|
+
return targets
|
37
|
+
}
|
38
|
+
const installTargetsToBeInstalled = targets.filter(target => target.isToBeInstalled)
|
39
|
+
if (installTargetsToBeInstalled.length) {
|
40
|
+
await eachOfSeriesProgress(
|
41
|
+
installTargetsToBeInstalled,
|
42
|
+
target => target.install({ clone: dev }),
|
43
|
+
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Installing plugins ${percentage}% complete`)
|
44
|
+
)
|
45
|
+
logger?.log(`${chalk.bold.cyan('<info>')} Installing plugins 100% complete`)
|
46
|
+
const manifestDependencies = await project.getManifestDependencies()
|
47
|
+
await updateManifest({ logger, project, targets, manifestDependencies, isInteractive })
|
48
|
+
}
|
49
|
+
await summariseInstallation({ logger, targets, dev })
|
50
|
+
return targets
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* @param {Object} options
|
55
|
+
* @param {Project} options.project
|
56
|
+
* @param {[string]} options.plugins
|
57
|
+
*/
|
58
|
+
async function getInstallTargets ({ logger, project, plugins, isCompatibleEnabled }) {
|
59
|
+
if (typeof plugins === 'string') plugins = [plugins]
|
60
|
+
/** whether adapt.json is being used to compile the list of plugins to install */
|
61
|
+
const isEmpty = !plugins?.length
|
62
|
+
/** a list of plugin name/version pairs */
|
63
|
+
const itinerary = isEmpty
|
64
|
+
? await project.getManifestDependencies()
|
65
|
+
: plugins.reduce((itinerary, arg) => {
|
66
|
+
const [name, version = '*'] = arg.split(/[#@]/)
|
67
|
+
// Duplicates are removed by assigning to object properties
|
68
|
+
itinerary[name] = version
|
69
|
+
return itinerary
|
70
|
+
}, {})
|
71
|
+
const pluginNames = Object.entries(itinerary).map(([name, version]) => `${name}#${version}`)
|
72
|
+
|
73
|
+
/**
|
74
|
+
* @type {[Target]}
|
75
|
+
*/
|
76
|
+
const targets = pluginNames.length
|
77
|
+
? pluginNames.map(nameVersion => {
|
78
|
+
const [name, requestedVersion] = nameVersion.split(/[#@]/)
|
79
|
+
return new Target({ name, requestedVersion, isCompatibleEnabled, project, logger })
|
80
|
+
})
|
81
|
+
: await project.getInstallTargets()
|
82
|
+
return targets
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* @param {Object} options
|
87
|
+
* @param {Project} options.project
|
88
|
+
* @param {[Target]} options.targets
|
89
|
+
*/
|
90
|
+
async function loadPluginData ({ logger, project, targets }) {
|
91
|
+
const frameworkVersion = project.version
|
92
|
+
await eachOfLimitProgress(
|
93
|
+
targets,
|
94
|
+
target => target.fetchSourceInfo(),
|
95
|
+
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Getting plugin info ${percentage}% complete`)
|
96
|
+
)
|
97
|
+
logger?.log(`${chalk.bold.cyan('<info>')} Getting plugin info 100% complete`)
|
98
|
+
await eachOfLimitProgress(
|
99
|
+
targets,
|
100
|
+
target => target.findCompatibleVersion(frameworkVersion),
|
101
|
+
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Finding compatible source versions ${percentage}% complete`)
|
102
|
+
)
|
103
|
+
logger?.log(`${chalk.bold.cyan('<info>')} Finding compatible source versions 100% complete`)
|
104
|
+
await eachOfLimitProgress(
|
105
|
+
targets,
|
106
|
+
target => target.markInstallable(),
|
107
|
+
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Marking installable ${percentage}% complete`)
|
108
|
+
)
|
109
|
+
logger?.log(`${chalk.bold.cyan('<info>')} Marking installable 100% complete`)
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* @param {Object} options
|
114
|
+
* @param {[Target]} options.targets
|
115
|
+
*/
|
116
|
+
async function conflictResolution ({ logger, targets, isInteractive, dev }) {
|
117
|
+
/** @param {Target} target */
|
118
|
+
async function checkVersion (target) {
|
119
|
+
const canApplyRequested = target.hasValidRequestVersion &&
|
120
|
+
(target.hasFrameworkCompatibleVersion
|
121
|
+
? (target.latestCompatibleSourceVersion !== target.matchedVersion)
|
122
|
+
: (target.latestSourceVersion !== target.matchedVersion))
|
123
|
+
if (!isInteractive) {
|
124
|
+
if (canApplyRequested) return target.markRequestedForInstallation()
|
125
|
+
return target.markSkipped()
|
126
|
+
}
|
127
|
+
const choices = [
|
128
|
+
dev && { name: 'master [master]', value: 'm' },
|
129
|
+
canApplyRequested && { name: `requested version [${target.matchedVersion}]`, value: 'r' },
|
130
|
+
target.hasFrameworkCompatibleVersion
|
131
|
+
? { name: `latest compatible version [${target.latestCompatibleSourceVersion}]`, value: 'l' }
|
132
|
+
: { name: `latest version [${target.latestSourceVersion}]`, value: 'l' },
|
133
|
+
{ name: 'skip', value: 's' }
|
134
|
+
].filter(Boolean)
|
135
|
+
const result = await createPromptTask({ message: chalk.reset(target.packageName), choices, type: 'list', default: 's' })
|
136
|
+
const installMasterBranch = (result === 'm')
|
137
|
+
const installRequested = (result === 'r')
|
138
|
+
const installLatest = result === 'l'
|
139
|
+
const skipped = result === 's'
|
140
|
+
if (installMasterBranch) target.markMasterForInstallation()
|
141
|
+
if (installRequested) target.markRequestedForInstallation()
|
142
|
+
if (installLatest && target.hasFrameworkCompatibleVersion) target.markLatestCompatibleForInstallation()
|
143
|
+
if (installLatest && !target.hasFrameworkCompatibleVersion) target.markLatestForInstallation()
|
144
|
+
if (skipped) target.markSkipped()
|
145
|
+
}
|
146
|
+
function add (list, header, prompt) {
|
147
|
+
if (!list.length) return
|
148
|
+
return {
|
149
|
+
header: chalk.cyan('<info> ') + header,
|
150
|
+
list,
|
151
|
+
prompt
|
152
|
+
}
|
153
|
+
}
|
154
|
+
const allQuestions = [
|
155
|
+
add(targets.filter(target => !target.hasFrameworkCompatibleVersion), 'There is no compatible version of the following plugins:', checkVersion),
|
156
|
+
add(targets.filter(target => target.hasFrameworkCompatibleVersion && !target.hasValidRequestVersion), 'The version requested is invalid, there are newer compatible versions of the following plugins:', checkVersion),
|
157
|
+
add(targets.filter(target => target.hasFrameworkCompatibleVersion && target.hasValidRequestVersion && !target.isApplyLatestCompatibleVersion), 'There are newer compatible versions of the following plugins:', checkVersion)
|
158
|
+
].filter(Boolean)
|
159
|
+
if (allQuestions.length === 0) return
|
160
|
+
for (const question of allQuestions) {
|
161
|
+
logger?.log(question.header)
|
162
|
+
await eachOfSeries(question.list, question.prompt)
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
/**
|
167
|
+
* @param {Object} options
|
168
|
+
* @param {Project} options.project
|
169
|
+
* @param {[Target]} options.targets
|
170
|
+
*/
|
171
|
+
async function updateManifest ({ project, targets, manifestDependencies, isInteractive }) {
|
172
|
+
if (targets.filter(target => target.isInstallSuccessful).length === 0) return
|
173
|
+
if (difference(targets.filter(target => target.isInstallSuccessful).map(target => target.packageName), Object.keys(manifestDependencies)).length === 0) return
|
174
|
+
if (isInteractive) {
|
175
|
+
const shouldUpdate = await createPromptTask({
|
176
|
+
message: chalk.white('Update the manifest (adapt.json)?'),
|
177
|
+
type: 'confirm',
|
178
|
+
default: true
|
179
|
+
})
|
180
|
+
if (!shouldUpdate) return
|
181
|
+
}
|
182
|
+
targets.forEach(target => target.isInstallSuccessful && project.add(target))
|
183
|
+
}
|
184
|
+
|
185
|
+
/**
|
186
|
+
* @param {Object} options
|
187
|
+
* @param {[Target]} options.targets
|
188
|
+
*/
|
189
|
+
function summariseDryRun ({ logger, targets }) {
|
190
|
+
const toBeInstalled = targets.filter(target => target.isToBeInstalled)
|
191
|
+
const toBeSkipped = targets.filter(target => !target.isToBeInstalled || target.isSkipped)
|
192
|
+
const missing = targets.filter(target => target.isMissing)
|
193
|
+
summarise(logger, toBeSkipped, packageNamePrinter, 'The following plugins will be skipped:')
|
194
|
+
summarise(logger, missing, packageNamePrinter, 'There was a problem locating the following plugins:')
|
195
|
+
summarise(logger, toBeInstalled, versionPrinter, 'The following plugins will be installed:')
|
196
|
+
}
|
197
|
+
|
198
|
+
/**
|
199
|
+
* @param {Object} options
|
200
|
+
* @param {[Target]} options.targets
|
201
|
+
*/
|
202
|
+
function summariseInstallation ({ logger, targets, dev }) {
|
203
|
+
const installSucceeded = targets.filter(target => target.isInstallSuccessful)
|
204
|
+
const installSkipped = targets.filter(target => !target.isToBeInstalled || target.isSkipped)
|
205
|
+
const installErrored = targets.filter(target => target.isInstallFailure)
|
206
|
+
const missing = targets.filter(target => target.isMissing)
|
207
|
+
const noneInstalled = (installSucceeded.length === 0)
|
208
|
+
const allInstalledSuccessfully = (installErrored.length === 0 && missing.length === 0)
|
209
|
+
const someInstalledSuccessfully = (!noneInstalled && !allInstalledSuccessfully)
|
210
|
+
summarise(logger, installSkipped, packageNamePrinter, 'The following plugins were skipped:')
|
211
|
+
summarise(logger, missing, packageNamePrinter, 'There was a problem locating the following plugins:')
|
212
|
+
summarise(logger, installErrored, errorPrinter, 'The following plugins could not be installed:')
|
213
|
+
if (noneInstalled) logger?.log(chalk.cyanBright('None of the requested plugins could be installed'))
|
214
|
+
else if (allInstalledSuccessfully) summarise(logger, installSucceeded, dev ? packageNamePrinter : versionPrinter, 'All requested plugins were successfully installed. Summary of installation:')
|
215
|
+
else if (someInstalledSuccessfully) summarise(logger, installSucceeded, dev ? packageNamePrinter : versionPrinter, 'The following plugins were successfully installed:')
|
216
|
+
}
|
217
|
+
|
218
|
+
function summarise (logger, list, iterator, header) {
|
219
|
+
if (!list || !iterator || list.length === 0) return
|
220
|
+
logger?.log(chalk.cyanBright(header))
|
221
|
+
list.forEach(item => iterator(item, logger))
|
222
|
+
}
|