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.
Files changed (129) hide show
  1. package/.bowerrc +2 -2
  2. package/.eslintignore +1 -0
  3. package/.eslintrc.json +14 -0
  4. package/.travis.yml +46 -46
  5. package/README.md +266 -266
  6. package/bin/adapt.js +3 -0
  7. package/json/help-create/component.json +9 -9
  8. package/json/help-create/course.json +9 -9
  9. package/json/help-create/question.json +9 -0
  10. package/json/help-create.json +12 -11
  11. package/json/help-devinstall.json +9 -9
  12. package/json/help-install.json +10 -10
  13. package/json/help-ls.json +7 -7
  14. package/json/help-register.json +7 -7
  15. package/json/help-rename.json +7 -7
  16. package/json/help-search.json +8 -8
  17. package/json/help-uninstall.json +7 -7
  18. package/json/help-unregister.json +8 -8
  19. package/json/help-update.json +12 -12
  20. package/json/help-version.json +7 -7
  21. package/json/help.json +19 -19
  22. package/lib/api.js +260 -0
  23. package/lib/cli.js +69 -52
  24. package/lib/commands/authenticate.js +18 -0
  25. package/lib/commands/create/component.js +64 -81
  26. package/lib/commands/create/course.js +26 -87
  27. package/lib/commands/create/question.js +18 -0
  28. package/lib/commands/create.js +85 -104
  29. package/lib/commands/devinstall.js +35 -97
  30. package/lib/commands/help.js +31 -52
  31. package/lib/commands/install.js +16 -856
  32. package/lib/commands/ls.js +9 -24
  33. package/lib/commands/register.js +11 -201
  34. package/lib/commands/rename.js +14 -138
  35. package/lib/commands/search.js +11 -29
  36. package/lib/commands/uninstall.js +9 -136
  37. package/lib/commands/unregister.js +12 -105
  38. package/lib/commands/update.js +12 -889
  39. package/lib/commands/version.js +13 -15
  40. package/lib/integration/AdaptFramework/build.js +39 -0
  41. package/lib/integration/AdaptFramework/clone.js +27 -0
  42. package/lib/integration/AdaptFramework/deleteSrcCore.js +9 -0
  43. package/lib/integration/AdaptFramework/deleteSrcCourse.js +9 -0
  44. package/lib/integration/AdaptFramework/download.js +21 -0
  45. package/lib/integration/AdaptFramework/erase.js +34 -0
  46. package/lib/integration/AdaptFramework/getLatestVersion.js +79 -0
  47. package/lib/integration/AdaptFramework/npmInstall.js +21 -0
  48. package/lib/integration/AdaptFramework.js +19 -0
  49. package/lib/integration/Plugin.js +403 -0
  50. package/lib/integration/PluginManagement/autenticate.js +56 -0
  51. package/lib/integration/PluginManagement/install.js +222 -0
  52. package/lib/integration/PluginManagement/print.js +52 -0
  53. package/lib/integration/PluginManagement/register.js +130 -0
  54. package/lib/integration/PluginManagement/rename.js +101 -0
  55. package/lib/integration/PluginManagement/schemas.js +8 -0
  56. package/lib/integration/PluginManagement/search.js +46 -0
  57. package/lib/integration/PluginManagement/uninstall.js +141 -0
  58. package/lib/integration/PluginManagement/unregister.js +101 -0
  59. package/lib/integration/PluginManagement/update.js +224 -0
  60. package/lib/integration/PluginManagement.js +21 -0
  61. package/lib/integration/Project.js +146 -0
  62. package/lib/integration/Target.js +296 -0
  63. package/lib/integration/getBowerRegistryConfig.js +34 -0
  64. package/lib/logger.js +28 -0
  65. package/lib/util/JSONReadValidate.js +34 -0
  66. package/lib/util/constants.js +38 -0
  67. package/lib/util/createPromptTask.js +7 -0
  68. package/lib/util/download.js +45 -0
  69. package/lib/util/errors.js +58 -0
  70. package/lib/util/extract.js +24 -0
  71. package/lib/util/getDirNameFromImportMeta.js +6 -0
  72. package/lib/util/promises.js +36 -0
  73. package/package.json +40 -49
  74. package/TESTING.md +0 -25
  75. package/bin/adapt +0 -3
  76. package/gruntfile.js +0 -18
  77. package/lib/AdaptConsoleApplication.js +0 -19
  78. package/lib/CommandParser.js +0 -19
  79. package/lib/CommandTranslator.js +0 -15
  80. package/lib/ConsoleRenderer.js +0 -10
  81. package/lib/Constants.js +0 -69
  82. package/lib/JsonLoader.js +0 -40
  83. package/lib/JsonWriter.js +0 -21
  84. package/lib/PackageMeta.js +0 -41
  85. package/lib/Plugin.js +0 -53
  86. package/lib/PluginTypeResolver.js +0 -47
  87. package/lib/Project.js +0 -89
  88. package/lib/RendererHelpers.js +0 -51
  89. package/lib/RepositoryDownloader.js +0 -64
  90. package/lib/Slug.js +0 -5
  91. package/lib/VersionChecker.js +0 -7
  92. package/lib/commands/create/index.js +0 -6
  93. package/lib/commands/index.js +0 -16
  94. package/lib/commands/install/InstallLog.js +0 -32
  95. package/lib/commands/install/InstallTarget.js +0 -259
  96. package/lib/commands/install/extend.js +0 -31
  97. package/lib/download.js +0 -101
  98. package/lib/errors.js +0 -58
  99. package/lib/promise/authenticate.js +0 -64
  100. package/lib/promise/build.js +0 -20
  101. package/lib/promise/cloneInstall.js +0 -35
  102. package/lib/promise/confirmBuild.js +0 -7
  103. package/lib/promise/exec.js +0 -39
  104. package/lib/promise/getRepository.js +0 -26
  105. package/lib/promise/highest.js +0 -109
  106. package/lib/promise/install.js +0 -31
  107. package/lib/promise/installAdaptDependencies.js +0 -30
  108. package/lib/promise/installNodeDependencies.js +0 -28
  109. package/lib/promise/removeTemporaryDownload.js +0 -8
  110. package/lib/promise/replaceTextContent.js +0 -10
  111. package/lib/promise/uninstallPackage.js +0 -15
  112. package/lib/promise/update.js +0 -33
  113. package/lib/promise/util.js +0 -16
  114. package/test/fixtures/adapt-with-plugins.json +0 -6
  115. package/test/specs/command_translation_concerns.js +0 -13
  116. package/test/specs/create_command_concerns.js +0 -22
  117. package/test/specs/create_concerns.js +0 -30
  118. package/test/specs/install_concerns.js +0 -31
  119. package/test/specs/installing_compatible_plugins_concerns.js +0 -126
  120. package/test/specs/installing_incompatible_plugins_concerns.js +0 -103
  121. package/test/specs/ls_concerns.js +0 -28
  122. package/test/specs/plugin_name_concerns.js +0 -82
  123. package/test/specs/project_concerns.js +0 -128
  124. package/test/specs/registration_concerns.js +0 -31
  125. package/test/specs/repository_downloader_concerns.js +0 -55
  126. package/test/specs/search_concerns.js +0 -30
  127. package/test/specs/type_resolution_concerns.js +0 -71
  128. package/test/specs/uninstall_command_concerns.js +0 -64
  129. package/test/specs/uninstall_concerns.js +0 -31
@@ -0,0 +1,224 @@
1
+ import chalk from 'chalk'
2
+ import { eachOfSeries } from 'async'
3
+ import path from 'path'
4
+ import Project from '../Project.js'
5
+ import { createPromptTask } from '../../util/createPromptTask.js'
6
+ import { errorPrinter, packageNamePrinter, versionPrinter, existingVersionPrinter } from './print.js'
7
+ import { eachOfLimitProgress, eachOfSeriesProgress } from '../../util/promises.js'
8
+ /** @typedef {import("../Target.js").default} Target */
9
+
10
+ export default async function update ({
11
+ plugins,
12
+ // whether to summarise installed plugins without modifying anything
13
+ isDryRun = false,
14
+ isInteractive = true,
15
+ cwd = process.cwd(),
16
+ logger = null
17
+ }) {
18
+ cwd = path.resolve(process.cwd(), cwd)
19
+ const project = new Project({ cwd, logger })
20
+ project.tryThrowInvalidPath()
21
+
22
+ logger?.log(chalk.cyan('update adapt dependencies...'))
23
+
24
+ const targets = await getUpdateTargets({ logger, project, plugins, isDryRun, isInteractive })
25
+ if (!targets?.length) return targets
26
+
27
+ await loadPluginData({ logger, project, targets })
28
+ await conflictResolution({ logger, targets, isInteractive })
29
+ if (isDryRun) {
30
+ await summariseDryRun({ logger, targets })
31
+ return targets
32
+ }
33
+ const updateTargetsToBeUpdated = targets.filter(target => target.isToBeInstalled)
34
+ if (updateTargetsToBeUpdated.length) {
35
+ await eachOfSeriesProgress(
36
+ updateTargetsToBeUpdated,
37
+ target => target.update(),
38
+ percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Updating plugins ${percentage}% complete`)
39
+ )
40
+ logger?.log(`${chalk.bold.cyan('<info>')} Updating plugins 100% complete`)
41
+ }
42
+ await summariseUpdates({ logger, targets })
43
+ return targets
44
+ }
45
+
46
+ /**
47
+ * @param {Object} options
48
+ * @param {Project} options.project
49
+ * @param {[string]} options.plugins
50
+ */
51
+ async function getUpdateTargets ({ project, plugins, isDryRun, isInteractive }) {
52
+ if (typeof plugins === 'string') plugins = [plugins]
53
+ if (!plugins) plugins = []
54
+ const allowedTypes = ['all', 'components', 'extensions', 'menu', 'theme']
55
+ const selectedTypes = [...new Set(plugins.filter(type => allowedTypes.includes(type)))]
56
+ const isEmpty = (!plugins.length)
57
+ const isAll = (isDryRun || isEmpty || selectedTypes.includes('all'))
58
+ const pluginNames = plugins
59
+ // remove types
60
+ .filter(arg => !allowedTypes.includes(arg))
61
+ // split name/version
62
+ .map(arg => {
63
+ const [name, version = '*'] = arg.split(/[#@]/)
64
+ return [name, version]
65
+ })
66
+ // make sure last applies
67
+ .reverse()
68
+
69
+ /** @type {[Target]} */
70
+ let targets = await project.getUpdateTargets()
71
+ for (const target of targets) {
72
+ await target.fetchProjectInfo()
73
+ }
74
+ if (!isDryRun && isEmpty && isInteractive) {
75
+ const shouldContinue = await createPromptTask({
76
+ message: chalk.reset('This command will attempt to update all installed plugins. Do you wish to continue?'),
77
+ type: 'confirm'
78
+ })
79
+ if (!shouldContinue) return
80
+ }
81
+ if (!isAll) {
82
+ const filtered = {}
83
+ for (const target of targets) {
84
+ const typeFolder = await target.getTypeFolder()
85
+ if (!typeFolder) continue
86
+ const lastSpecifiedPluginName = pluginNames.find(([name]) => target.isNameMatch(name))
87
+ const isPluginNameIncluded = Boolean(lastSpecifiedPluginName)
88
+ const isTypeIncluded = selectedTypes.includes(typeFolder)
89
+ if (!isPluginNameIncluded && !isTypeIncluded) continue
90
+ // Resolve duplicates
91
+ filtered[target.packageName] = target
92
+ // Set requested version from name
93
+ target.requestedVersion = lastSpecifiedPluginName[1] || '*'
94
+ }
95
+ targets = Object.values(filtered)
96
+ }
97
+ return targets
98
+ }
99
+
100
+ /**
101
+ * @param {Object} options
102
+ * @param {Project} options.project
103
+ * @param {[Target]} options.targets
104
+ */
105
+ async function loadPluginData ({ logger, project, targets }) {
106
+ const frameworkVersion = project.version
107
+ await eachOfLimitProgress(
108
+ targets,
109
+ target => target.fetchSourceInfo(),
110
+ percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Getting plugin info ${percentage}% complete`)
111
+ )
112
+ logger?.log(`${chalk.bold.cyan('<info>')} Getting plugin info 100% complete`)
113
+ await eachOfLimitProgress(
114
+ targets,
115
+ target => target.findCompatibleVersion(frameworkVersion),
116
+ percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Finding compatible source versions ${percentage}% complete`)
117
+ )
118
+ logger?.log(`${chalk.bold.cyan('<info>')} Finding compatible source versions 100% complete`)
119
+ await eachOfLimitProgress(
120
+ targets,
121
+ target => target.markUpdateable(),
122
+ percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Marking updateable ${percentage}% complete`)
123
+ )
124
+ logger?.log(`${chalk.bold.cyan('<info>')} Marking updateable 100% complete`)
125
+ }
126
+
127
+ /**
128
+ * @param {Object} options
129
+ * @param {[Target]} options.targets
130
+ */
131
+ async function conflictResolution ({ logger, targets, isInteractive }) {
132
+ /** @param {Target} target */
133
+ async function checkVersion (target) {
134
+ const canApplyRequested = target.hasValidRequestVersion &&
135
+ (target.hasFrameworkCompatibleVersion
136
+ ? (target.latestCompatibleSourceVersion !== target.matchedVersion)
137
+ : (target.latestSourceVersion !== target.matchedVersion))
138
+ if (!isInteractive) {
139
+ if (target.canApplyRequested) return target.markRequestedForInstallation()
140
+ return target.markSkipped()
141
+ }
142
+ const choices = [
143
+ canApplyRequested && { name: `requested version [${target.matchedVersion}]`, value: 'r' },
144
+ target.hasFrameworkCompatibleVersion
145
+ ? { name: `latest compatible version [${target.latestCompatibleSourceVersion}]`, value: 'l' }
146
+ : { name: `latest version [${target.latestSourceVersion}]`, value: 'l' },
147
+ { name: 'skip', value: 's' }
148
+ ].filter(Boolean)
149
+ const result = await createPromptTask({ message: chalk.reset(target.packageName), choices, type: 'list', default: 's' })
150
+ const installRequested = (result === 'r')
151
+ const installLatest = result === 'l'
152
+ const skipped = result === 's'
153
+ if (installRequested) target.markRequestedForInstallation()
154
+ if (installLatest && target.hasFrameworkCompatibleVersion) target.markLatestCompatibleForInstallation()
155
+ if (installLatest && !target.hasFrameworkCompatibleVersion) target.markLatestForInstallation()
156
+ if (skipped) target.markSkipped()
157
+ }
158
+ function add (list, header, prompt) {
159
+ if (!list.length) return
160
+ return {
161
+ header: chalk.bold.cyan('<info> ') + header,
162
+ list,
163
+ prompt
164
+ }
165
+ }
166
+ const preFilteredPlugins = targets.filter(target => !target.isLocalSource)
167
+ const allQuestions = [
168
+ add(preFilteredPlugins.filter(target => !target.hasFrameworkCompatibleVersion), 'There is no compatible version of the following plugins:', checkVersion),
169
+ add(preFilteredPlugins.filter(target => target.hasFrameworkCompatibleVersion && !target.hasValidRequestVersion), 'The version requested is invalid, there are newer compatible versions of the following plugins:', checkVersion),
170
+ add(preFilteredPlugins.filter(target => target.hasFrameworkCompatibleVersion && target.hasValidRequestVersion && !target.isApplyLatestCompatibleVersion), 'There are newer compatible versions of the following plugins:', checkVersion)
171
+ ].filter(Boolean)
172
+ if (allQuestions.length === 0) return
173
+ for (const question of allQuestions) {
174
+ logger?.log(question.header)
175
+ await eachOfSeries(question.list, question.prompt)
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @param {Object} options
181
+ * @param {[Target]} options.targets
182
+ */
183
+ function summariseDryRun ({ logger, targets }) {
184
+ const preFilteredPlugins = targets.filter(target => !target.isLocalSource)
185
+ const localSources = targets.filter(target => target.isLocalSource)
186
+ const toBeInstalled = preFilteredPlugins.filter(target => target.isToBeUpdated)
187
+ const toBeSkipped = preFilteredPlugins.filter(target => !target.isToBeUpdated || target.isSkipped)
188
+ const missing = preFilteredPlugins.filter(target => target.isMissing)
189
+ summarise(logger, localSources, packageNamePrinter, 'The following plugins were installed from a local source and cannot be updated:')
190
+ summarise(logger, toBeSkipped, packageNamePrinter, 'The following plugins will be skipped:')
191
+ summarise(logger, missing, packageNamePrinter, 'There was a problem locating the following plugins:')
192
+ summarise(logger, toBeInstalled, versionPrinter, 'The following plugins will be updated:')
193
+ }
194
+
195
+ /**
196
+ * @param {Object} options
197
+ * @param {[Target]} options.targets
198
+ */
199
+ function summariseUpdates ({ logger, targets }) {
200
+ const preFilteredPlugins = targets.filter(target => !target.isLocalSource)
201
+ const localSources = targets.filter(target => target.isLocalSource)
202
+ const installSucceeded = preFilteredPlugins.filter(target => target.isUpdateSuccessful)
203
+ const installSkipped = preFilteredPlugins.filter(target => target.isSkipped)
204
+ const noUpdateAvailable = preFilteredPlugins.filter(target => !target.isToBeUpdated && !target.isSkipped)
205
+ const installErrored = preFilteredPlugins.filter(target => target.isUpdateFailure)
206
+ const missing = preFilteredPlugins.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, localSources, existingVersionPrinter, 'The following plugins were installed from a local source and cannot be updated:')
211
+ summarise(logger, installSkipped, existingVersionPrinter, 'The following plugins were skipped:')
212
+ summarise(logger, noUpdateAvailable, existingVersionPrinter, 'The following plugins had no update available:')
213
+ summarise(logger, missing, packageNamePrinter, 'There was a problem locating the following plugins:')
214
+ summarise(logger, installErrored, errorPrinter, 'The following plugins could not be updated:')
215
+ if (noneInstalled) logger?.log(chalk.cyanBright('None of the requested plugins could be updated'))
216
+ else if (allInstalledSuccessfully) summarise(logger, installSucceeded, existingVersionPrinter, 'All requested plugins were successfully updated. Summary of installation:')
217
+ else if (someInstalledSuccessfully) summarise(logger, installSucceeded, existingVersionPrinter, 'The following plugins were successfully updated:')
218
+ }
219
+
220
+ function summarise (logger, list, iterator, header) {
221
+ if (!list || !iterator || list.length === 0) return
222
+ logger?.log(chalk.cyanBright(header))
223
+ list.forEach(item => iterator(item, logger))
224
+ }
@@ -0,0 +1,21 @@
1
+ import authenticate from './PluginManagement/autenticate.js'
2
+ import install from './PluginManagement/install.js'
3
+ import register from './PluginManagement/register.js'
4
+ import rename from './PluginManagement/rename.js'
5
+ import schemas from './PluginManagement/schemas.js'
6
+ import search from './PluginManagement/search.js'
7
+ import uninstall from './PluginManagement/uninstall.js'
8
+ import unregister from './PluginManagement/unregister.js'
9
+ import update from './PluginManagement/update.js'
10
+
11
+ export {
12
+ authenticate,
13
+ install,
14
+ register,
15
+ rename,
16
+ schemas,
17
+ search,
18
+ uninstall,
19
+ unregister,
20
+ update
21
+ }
@@ -0,0 +1,146 @@
1
+ import fs from 'fs-extra'
2
+ import path from 'path'
3
+ import globs from 'globs'
4
+ import { readValidateJSON, readValidateJSONSync } from '../util/JSONReadValidate.js'
5
+ import Plugin from './Plugin.js'
6
+ import Target from './Target.js'
7
+ export const MANIFEST_FILENAME = 'adapt.json'
8
+ export const FRAMEWORK_FILENAME = 'package.json'
9
+
10
+ /**
11
+ * A representation of the target Adapt Framework project
12
+ */
13
+ export default class Project {
14
+ constructor ({
15
+ cwd = process.cwd(),
16
+ logger
17
+ } = {}) {
18
+ this.logger = logger
19
+ this.cwd = cwd
20
+ this.manifestFilePath = path.resolve(this.cwd, MANIFEST_FILENAME)
21
+ this.frameworkPackagePath = path.resolve(this.cwd, FRAMEWORK_FILENAME)
22
+ }
23
+
24
+ /** @returns {boolean} */
25
+ get isAdaptDirectory () {
26
+ try {
27
+ // are we inside an existing adapt_framework project.
28
+ const packageJSON = fs.readJSONSync(this.cwd + '/package.json')
29
+ return (packageJSON.name === 'adapt_framework')
30
+ } catch (err) {
31
+ // don't worry, we're not inside a framework directory.
32
+ }
33
+ return false
34
+ }
35
+
36
+ /** @returns {boolean} */
37
+ get containsManifestFile () {
38
+ if (!this.isAdaptDirectory) return false
39
+ return fs.existsSync(this.manifestFilePath)
40
+ }
41
+
42
+ /** @returns {string} */
43
+ get version () {
44
+ try {
45
+ return readValidateJSONSync(this.frameworkPackagePath).version
46
+ } catch (ex) {
47
+ return null
48
+ }
49
+ }
50
+
51
+ tryThrowInvalidPath () {
52
+ if (this.containsManifestFile) return
53
+ this.logger?.error('Fatal error: please run above commands in adapt course directory.')
54
+ throw new Error('Fatal error: please run above commands in adapt course directory.')
55
+ }
56
+
57
+ /** @returns {[Target]} */
58
+ async getInstallTargets () {
59
+ return Object.entries(await this.getManifestDependencies()).map(([name, requestedVersion]) => new Target({ name, requestedVersion, project: this, logger: this.logger }))
60
+ }
61
+
62
+ /** @returns {[string]} */
63
+ async getManifestDependencies () {
64
+ const manifest = await readValidateJSON(this.manifestFilePath)
65
+ return manifest.dependencies
66
+ }
67
+
68
+ /** @returns {[Plugin]} */
69
+ async getInstalledPlugins () {
70
+ return Object.entries(await this.getInstalledDependencies()).map(([name]) => new Plugin({ name, project: this, logger: this.logger }))
71
+ }
72
+
73
+ /** @returns {[Target]} */
74
+ async getUninstallTargets () {
75
+ return Object.entries(await this.getInstalledDependencies()).map(([name]) => new Target({ name, project: this, logger: this.logger }))
76
+ }
77
+
78
+ /** @returns {[Target]} */
79
+ async getUpdateTargets () {
80
+ return Object.entries(await this.getInstalledDependencies()).map(([name]) => new Target({ name, project: this, logger: this.logger }))
81
+ }
82
+
83
+ async getInstalledDependencies () {
84
+ const getDependencyBowerJSONs = async () => {
85
+ const glob = `${this.cwd.replace(/\\/g, '/')}/src/**/bower.json`
86
+ const bowerJSONPaths = await new Promise((resolve, reject) => {
87
+ globs(glob, (err, matches) => {
88
+ if (err) return reject(err)
89
+ resolve(matches)
90
+ })
91
+ })
92
+ const bowerJSONs = []
93
+ for (const bowerJSONPath of bowerJSONPaths) {
94
+ try {
95
+ bowerJSONs.push(await fs.readJSON(bowerJSONPath))
96
+ } catch (err) {}
97
+ }
98
+ return bowerJSONs
99
+ }
100
+ const dependencies = (await getDependencyBowerJSONs())
101
+ .filter(bowerJSON => bowerJSON?.name && bowerJSON?.version)
102
+ .reduce((dependencies, bowerJSON) => {
103
+ dependencies[bowerJSON.name] = bowerJSON.version
104
+ return dependencies
105
+ }, {})
106
+ return dependencies
107
+ }
108
+
109
+ async getSchemaPaths () {
110
+ const glob = `${this.cwd.replace(/\\/g, '/')}/src/**/*.schema.json`
111
+ const bowerJSONPaths = await new Promise((resolve, reject) => {
112
+ globs(glob, (err, matches) => {
113
+ if (err) return reject(err)
114
+ resolve(matches)
115
+ })
116
+ })
117
+ return bowerJSONPaths
118
+ }
119
+
120
+ /**
121
+ * @param {Plugin} plugin
122
+ */
123
+ add (plugin) {
124
+ if (typeof Plugin !== 'object' && !(plugin instanceof Plugin)) {
125
+ plugin = new Plugin({ name: plugin })
126
+ }
127
+ let manifest = { version: '0.0.0', dependencies: {} }
128
+ if (this.containsManifestFile) {
129
+ manifest = readValidateJSONSync(this.manifestFilePath)
130
+ }
131
+ manifest.dependencies[plugin.packageName] = plugin.sourcePath || plugin.requestedVersion || plugin.version
132
+ fs.writeJSONSync(this.manifestFilePath, manifest, { spaces: 2, replacer: null })
133
+ }
134
+
135
+ /**
136
+ * @param {Plugin} plugin
137
+ */
138
+ remove (plugin) {
139
+ if (typeof Plugin !== 'object' && !(plugin instanceof Plugin)) {
140
+ plugin = new Plugin({ name: plugin })
141
+ }
142
+ const manifest = readValidateJSONSync(this.manifestFilePath)
143
+ delete manifest.dependencies[plugin.packageName]
144
+ fs.writeJSONSync(this.manifestFilePath, manifest, { spaces: 2, replacer: null })
145
+ }
146
+ }
@@ -0,0 +1,296 @@
1
+ import chalk from 'chalk'
2
+ import bower from 'bower'
3
+ import { exec } from 'child_process'
4
+ import semver from 'semver'
5
+ import fs from 'fs-extra'
6
+ import path from 'path'
7
+ import { ADAPT_ALLOW_PRERELEASE } from '../util/constants.js'
8
+ import Plugin from './Plugin.js'
9
+ /** @typedef {import("./Project.js").default} Project */
10
+ const semverOptions = { includePrerelease: ADAPT_ALLOW_PRERELEASE }
11
+
12
+ export default class Target extends Plugin {
13
+ /**
14
+ * @param {Object} options
15
+ * @param {string} options.name
16
+ * @param {string} options.requestedVersion
17
+ * @param {boolean} options.isContrib
18
+ * @param {boolean} options.isCompatibleEnabled whether to target the latest compatible version for all plugin installations (overrides requestedVersion)
19
+ * @param {Project} options.project
20
+ * @param {string} options.cwd
21
+ * @param {Object} options.logger
22
+ */
23
+ constructor ({
24
+ name,
25
+ requestedVersion = '*',
26
+ isContrib = false,
27
+ isCompatibleEnabled = false,
28
+ project,
29
+ cwd = (project?.cwd ?? process.cwd()),
30
+ logger
31
+ } = {}) {
32
+ super({
33
+ name,
34
+ requestedVersion,
35
+ isContrib,
36
+ isCompatibleEnabled,
37
+ project,
38
+ cwd,
39
+ logger
40
+ })
41
+ // The version to be installed
42
+ this.versionToApply = null
43
+ // Keep the project version preupdate
44
+ this.preUpdateProjectVersion = null
45
+ // Was explicitly skipped by the user
46
+ this._isSkipped = null
47
+ // Marks that this target was uninstalled, true, false and null
48
+ this._wasUninstalled = null
49
+ }
50
+
51
+ /**
52
+ * Was explicitly skipped by the user
53
+ * @returns {boolean}
54
+ */
55
+ get isSkipped () {
56
+ return Boolean(this._isSkipped)
57
+ }
58
+
59
+ get isNoApply () {
60
+ return (this.isPresent && this.versionToApply === null)
61
+ }
62
+
63
+ /** @returns {boolean} */
64
+ get hasProposedVersion () {
65
+ return (this.matchedVersion !== null)
66
+ }
67
+
68
+ /** @returns {boolean} */
69
+ get isToBeInstalled () {
70
+ return (this.versionToApply !== null && !this._isSkipped)
71
+ }
72
+
73
+ /** @returns {boolean} */
74
+ get isInstallSuccessful () {
75
+ return (this.isToBeInstalled && this.isUpToDate)
76
+ }
77
+
78
+ /** @returns {boolean} */
79
+ get isInstallFailure () {
80
+ return (this.isToBeInstalled && !this.isUpToDate)
81
+ }
82
+
83
+ /** @returns {boolean} */
84
+ get isToBeUninstalled () {
85
+ return (this.versionToApply !== null && !this._isSkipped)
86
+ }
87
+
88
+ /** @returns {boolean} */
89
+ get isUninstallSuccessful () {
90
+ return (this.isToBeUninstalled && this._wasUninstalled)
91
+ }
92
+
93
+ /** @returns {boolean} */
94
+ get isUninstallFailure () {
95
+ return (this.isToBeUninstalled && !this._wasUninstalled)
96
+ }
97
+
98
+ /** @returns {boolean} */
99
+ get isToBeUpdated () {
100
+ return (this.versionToApply !== null && !this._isSkipped)
101
+ }
102
+
103
+ /** @returns {boolean} */
104
+ get isUpdateSuccessful () {
105
+ return (this.isToBeUpdated && this.isUpToDate)
106
+ }
107
+
108
+ /** @returns {boolean} */
109
+ get isUpdateFailure () {
110
+ return (this.isToBeUpdated && !this.isUpToDate)
111
+ }
112
+
113
+ /** @returns {boolean} */
114
+ get isApplyLatestCompatibleVersion () {
115
+ return (this.hasFrameworkCompatibleVersion &&
116
+ semver.satisfies(this.latestCompatibleSourceVersion, this.matchedVersion, semverOptions))
117
+ }
118
+
119
+ markSkipped () {
120
+ this._isSkipped = true
121
+ }
122
+
123
+ markInstallable () {
124
+ if (!this.isApplyLatestCompatibleVersion && !(this.isLocalSource && this.latestSourceVersion)) return
125
+ this.versionToApply = this.matchedVersion
126
+ }
127
+
128
+ markUpdateable () {
129
+ if (!this.isPresent || this.isSkipped || !this.canBeUpdated) return
130
+ if (this.projectVersion === this.matchedVersion) return
131
+ this.versionToApply = this.matchedVersion
132
+ }
133
+
134
+ markMasterForInstallation () {
135
+ this.versionToApply = '*'
136
+ }
137
+
138
+ markRequestedForInstallation () {
139
+ this.matchedVersion = this.matchedVersion ?? semver.maxSatisfying(this.sourceVersions, this.requestedVersion, semverOptions)
140
+ if (this.projectVersion === this.matchedVersion) return
141
+ this.versionToApply = this.matchedVersion
142
+ }
143
+
144
+ markLatestCompatibleForInstallation () {
145
+ if (this.projectVersion === this.latestCompatibleSourceVersion) return
146
+ this.versionToApply = this.latestCompatibleSourceVersion
147
+ }
148
+
149
+ markLatestForInstallation () {
150
+ if (this.projectVersion === this.latestSourceVersion) return
151
+ this.versionToApply = this.latestSourceVersion
152
+ }
153
+
154
+ markUninstallable () {
155
+ if (!this.isPresent) return
156
+ this.versionToApply = this.projectVersion
157
+ }
158
+
159
+ async install ({ clone = false } = {}) {
160
+ const logger = this.logger
161
+ const pluginTypeFolder = await this.getTypeFolder()
162
+ if (this.isLocalSource) {
163
+ await fs.ensureDir(path.resolve(this.cwd, 'src', pluginTypeFolder))
164
+ const pluginPath = path.resolve(this.cwd, 'src', pluginTypeFolder, this.name)
165
+ await fs.rm(pluginPath, { recursive: true, force: true })
166
+ await fs.copy(this.sourcePath, pluginPath, { recursive: true })
167
+ const bowerJSON = await fs.readJSON(path.join(pluginPath, 'bower.json'))
168
+ bowerJSON._source = this.sourcePath
169
+ bowerJSON._wasInstalledFromPath = true
170
+ await fs.writeJSON(path.join(pluginPath, '.bower.json'), bowerJSON, { spaces: 2, replacer: null })
171
+ this._projectInfo = null
172
+ await this.fetchProjectInfo()
173
+ return
174
+ }
175
+ if (clone) {
176
+ // clone install
177
+ const repoDetails = await this.getRepositoryUrl()
178
+ if (!repoDetails) throw new Error('Error: Plugin repository url could not be found.')
179
+ await fs.ensureDir(path.resolve(this.cwd, 'src', pluginTypeFolder))
180
+ const pluginPath = path.resolve(this.cwd, 'src', pluginTypeFolder, this.name)
181
+ await fs.rm(pluginPath, { recursive: true, force: true })
182
+ const url = repoDetails.url.replace(/^git:\/\//, 'https://')
183
+ try {
184
+ const exitCode = await new Promise((resolve, reject) => {
185
+ try {
186
+ exec(`git clone ${url} "${pluginPath}"`, resolve)
187
+ } catch (err) {
188
+ reject(err)
189
+ }
190
+ })
191
+ if (exitCode) throw new Error(`The plugin was found but failed to download and install. Exit code ${exitCode}`)
192
+ } catch (error) {
193
+ throw new Error(`The plugin was found but failed to download and install. Error ${error}`)
194
+ }
195
+ if (this.versionToApply !== '*') {
196
+ try {
197
+ await new Promise(resolve => exec(`git -C "${pluginPath}" checkout v${this.versionToApply}`, resolve))
198
+ logger?.log(chalk.green(this.packageName), `is on branch "${this.versionToApply}".`)
199
+ } catch (err) {
200
+ throw new Error(chalk.yellow(this.packageName), `could not checkout branch "${this.versionToApply}".`)
201
+ }
202
+ }
203
+ this._projectInfo = null
204
+ await this.fetchProjectInfo()
205
+ return
206
+ }
207
+ // bower install
208
+ const outputPath = path.join(this.cwd, 'src', pluginTypeFolder)
209
+ const pluginPath = path.join(outputPath, this.name)
210
+ try {
211
+ await fs.rm(pluginPath, { recursive: true, force: true })
212
+ } catch (err) {
213
+ throw new Error(`There was a problem writing to the target directory ${pluginPath}`)
214
+ }
215
+ await new Promise((resolve, reject) => {
216
+ const pluginNameVersion = `${this.packageName}@${this.versionToApply}`
217
+ bower.commands.install([pluginNameVersion], null, {
218
+ directory: outputPath,
219
+ cwd: this.cwd,
220
+ registry: this.BOWER_REGISTRY_CONFIG
221
+ })
222
+ .on('end', resolve)
223
+ .on('error', err => {
224
+ err = new Error(`Bower reported ${err}`)
225
+ this._error = err
226
+ reject(err)
227
+ })
228
+ })
229
+ this._projectInfo = null
230
+ await this.fetchProjectInfo()
231
+ }
232
+
233
+ async update () {
234
+ if (!this.isToBeUpdated) throw new Error()
235
+ const typeFolder = await this.getTypeFolder()
236
+ const outputPath = path.join(this.cwd, 'src', typeFolder)
237
+ const pluginPath = path.join(outputPath, this.name)
238
+ try {
239
+ await fs.rm(pluginPath, { recursive: true, force: true })
240
+ } catch (err) {
241
+ throw new Error(`There was a problem writing to the target directory ${pluginPath}`)
242
+ }
243
+ await new Promise((resolve, reject) => {
244
+ const pluginNameVersion = `${this.packageName}@${this.matchedVersion}`
245
+ bower.commands.install([pluginNameVersion], null, {
246
+ directory: outputPath,
247
+ cwd: this.cwd,
248
+ registry: this.BOWER_REGISTRY_CONFIG
249
+ })
250
+ .on('end', resolve)
251
+ .on('error', err => {
252
+ err = new Error(`Bower reported ${err}`)
253
+ this._error = err
254
+ reject(err)
255
+ })
256
+ })
257
+ this.preUpdateProjectVersion = this.projectVersion
258
+ this._projectInfo = null
259
+ await this.fetchProjectInfo()
260
+ }
261
+
262
+ async uninstall () {
263
+ try {
264
+ if (!this.isToBeUninstalled) throw new Error()
265
+ await fs.rm(this.projectPath, { recursive: true, force: true })
266
+ this._wasUninstalled = true
267
+ } catch (err) {
268
+ this._wasUninstalled = false
269
+ throw new Error(`There was a problem writing to the target directory ${this.projectPath}`)
270
+ }
271
+ }
272
+
273
+ isNameMatch (name) {
274
+ const tester = new RegExp(`${name}$`, 'i')
275
+ return tester.test(this.packageName)
276
+ }
277
+
278
+ /**
279
+ * Read plugin data from pluginPath
280
+ * @param {Object} options
281
+ * @param {string} options.pluginPath Path to source directory
282
+ * @param {string} [options.projectPath=process.cwd()] Optional path to potential installation project
283
+ * @returns {Target}
284
+ */
285
+ static async fromPath ({
286
+ pluginPath,
287
+ projectPath = process.cwd()
288
+ }) {
289
+ const target = new Target({
290
+ name: pluginPath,
291
+ cwd: projectPath
292
+ })
293
+ await target.fetchLocalSourceInfo()
294
+ return target
295
+ }
296
+ }