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,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
|
+
}
|