-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathinstall.js
More file actions
230 lines (218 loc) · 10.2 KB
/
install.js
File metadata and controls
230 lines (218 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import chalk from 'chalk'
import { eachOfSeries } from 'async'
import { createPromptTask } from '../../util/createPromptTask.js'
import { errorPrinter, packageNamePrinter, versionPrinter } from './print.js'
import { eachOfLimitProgress, eachOfSeriesProgress } from '../../util/promises.js'
import Project from '../Project.js'
import Target from '../Target.js'
import bower from 'bower'
import { difference } from 'lodash-es'
import path from 'path'
export default async function install ({
plugins,
dev = false,
isInteractive = true,
isDryRun = false, // whether to summarise installation without modifying anything
isCompatibleEnabled = false,
isClean = false,
cwd = process.cwd(),
logger = null
}) {
cwd = path.resolve(process.cwd(), cwd)
isClean && await new Promise(resolve => bower.commands.cache.clean().on('end', resolve))
const project = new Project({ cwd, logger })
project.tryThrowInvalidPath()
logger?.log(chalk.cyan(`${dev ? 'cloning' : 'installing'} adapt dependencies...`))
const targets = await getInstallTargets({ logger, project, plugins, isCompatibleEnabled })
if (!targets?.length) return targets
await loadPluginData({ logger, project, targets })
await conflictResolution({ logger, targets, isInteractive, dev })
if (isDryRun) {
await summariseDryRun({ logger, targets })
return targets
}
const installTargetsToBeInstalled = targets.filter(target => target.isToBeInstalled)
if (installTargetsToBeInstalled.length) {
await eachOfSeriesProgress(
installTargetsToBeInstalled,
target => target.install({ clone: dev }),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Installing plugins ${percentage}% complete`)
)
logger?.log(`${chalk.bold.cyan('<info>')} Installing plugins 100% complete`)
const manifestDependencies = await project.getManifestDependencies()
await updateManifest({ logger, project, targets, manifestDependencies, isInteractive })
}
await summariseInstallation({ logger, targets, dev })
return targets
}
/**
* @param {Object} options
* @param {Project} options.project
* @param {[string]} options.plugins
*/
async function getInstallTargets ({ logger, project, plugins, isCompatibleEnabled }) {
if (typeof plugins === 'string') plugins = [plugins]
/** whether adapt.json is being used to compile the list of plugins to install */
const isEmpty = !plugins?.length
/** a list of plugin name/version pairs */
const itinerary = isEmpty
? await project.getManifestDependencies()
: plugins.reduce((itinerary, arg) => {
const [name, version = '*'] = arg.split(/[#@]/)
// Duplicates are removed by assigning to object properties
itinerary[name] = version
return itinerary
}, {})
const pluginNames = Object.entries(itinerary).map(([name, version]) => `${name}#${version}`)
/**
* @type {[Target]}
*/
const targets = pluginNames.length
? pluginNames.map(nameVersion => {
const [name, requestedVersion] = nameVersion.split(/[#@]/)
return new Target({ name, requestedVersion, isCompatibleEnabled, project, logger })
})
: await project.getInstallTargets()
return targets
}
/**
* @param {Object} options
* @param {Project} options.project
* @param {[Target]} options.targets
*/
async function loadPluginData ({ logger, project, targets }) {
const frameworkVersion = project.version
await eachOfLimitProgress(
targets,
target => target.fetchSourceInfo(),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Getting plugin info ${percentage}% complete`)
)
logger?.log(`${chalk.bold.cyan('<info>')} Getting plugin info 100% complete`)
await eachOfLimitProgress(
targets,
target => target.fetchProjectInfo(),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Checking installed plugins ${percentage}% complete`)
)
logger?.log(`${chalk.bold.cyan('<info>')} Checking installed plugins 100% complete`)
await eachOfLimitProgress(
targets,
target => target.findCompatibleVersion(frameworkVersion),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Finding compatible source versions ${percentage}% complete`)
)
logger?.log(`${chalk.bold.cyan('<info>')} Finding compatible source versions 100% complete`)
await eachOfLimitProgress(
targets,
target => target.markInstallable(),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Marking installable ${percentage}% complete`)
)
logger?.log(`${chalk.bold.cyan('<info>')} Marking installable 100% complete`)
}
/**
* @param {Object} options
* @param {[Target]} options.targets
*/
async function conflictResolution ({ logger, targets, isInteractive, dev }) {
/** @param {Target} target */
async function checkVersion (target) {
const canApplyRequested = target.hasValidRequestVersion &&
(target.hasFrameworkCompatibleVersion
? (target.latestCompatibleSourceVersion !== target.matchedVersion)
: (target.latestSourceVersion !== target.matchedVersion))
if (!isInteractive) {
if (canApplyRequested) return target.markRequestedForInstallation()
return target.markSkipped()
}
const choices = [
dev && { name: 'master [master]', value: 'm' },
canApplyRequested && { name: `requested version [${target.matchedVersion}]`, value: 'r' },
target.hasFrameworkCompatibleVersion
? { name: `latest compatible version [${target.latestCompatibleSourceVersion}]`, value: 'l' }
: target.latestSourceVersion
? { name: `latest version [${target.latestSourceVersion}]`, value: 'l' }
: { name: 'master [master]', value: 'm' },
{ name: 'skip', value: 's' }
].filter(Boolean)
const result = await createPromptTask({ message: chalk.reset(target.packageName), choices, type: 'list', default: 's' })
const installMasterBranch = (result === 'm')
const installRequested = (result === 'r')
const installLatest = result === 'l'
const skipped = result === 's'
if (installMasterBranch) target.markMasterForInstallation()
if (installRequested) target.markRequestedForInstallation()
if (installLatest && target.hasFrameworkCompatibleVersion) target.markLatestCompatibleForInstallation()
if (installLatest && !target.hasFrameworkCompatibleVersion) target.markLatestForInstallation()
if (skipped) target.markSkipped()
}
function add (list, header, prompt) {
if (!list.length) return
return {
header: chalk.cyan('<info> ') + header,
list,
prompt
}
}
const allQuestions = [
add(targets.filter(target => !target.hasFrameworkCompatibleVersion), 'There is no compatible version of the following plugins:', checkVersion),
add(targets.filter(target => target.hasFrameworkCompatibleVersion && !target.hasValidRequestVersion), 'The version requested is invalid, there are newer compatible versions of the following plugins:', checkVersion),
add(targets.filter(target => target.hasFrameworkCompatibleVersion && target.hasValidRequestVersion && !target.isApplyLatestCompatibleVersion), 'There are newer compatible versions of the following plugins:', checkVersion)
].filter(Boolean)
if (allQuestions.length === 0) return
for (const question of allQuestions) {
logger?.log(question.header)
await eachOfSeries(question.list, question.prompt)
}
}
/**
* @param {Object} options
* @param {Project} options.project
* @param {[Target]} options.targets
*/
async function updateManifest ({ project, targets, manifestDependencies, isInteractive }) {
if (targets.filter(target => target.isInstallSuccessful).length === 0) return
if (difference(targets.filter(target => target.isInstallSuccessful).map(target => target.packageName), Object.keys(manifestDependencies)).length === 0) return
if (isInteractive) {
const shouldUpdate = await createPromptTask({
message: chalk.white('Update the manifest (adapt.json)?'),
type: 'confirm',
default: true
})
if (!shouldUpdate) return
}
targets.forEach(target => target.isInstallSuccessful && project.add(target))
}
/**
* @param {Object} options
* @param {[Target]} options.targets
*/
function summariseDryRun ({ logger, targets }) {
const toBeInstalled = targets.filter(target => target.isToBeInstalled)
const toBeSkipped = targets.filter(target => !target.isToBeInstalled || target.isSkipped)
const missing = targets.filter(target => target.isMissing)
summarise(logger, toBeSkipped, packageNamePrinter, 'The following plugins will be skipped:')
summarise(logger, missing, packageNamePrinter, 'There was a problem locating the following plugins:')
summarise(logger, toBeInstalled, versionPrinter, 'The following plugins will be installed:')
}
/**
* @param {Object} options
* @param {[Target]} options.targets
*/
function summariseInstallation ({ logger, targets, dev }) {
const installSucceeded = targets.filter(target => target.isInstallSuccessful)
const installSkipped = targets.filter(target => !target.isToBeInstalled || target.isSkipped)
const installErrored = targets.filter(target => target.isInstallFailure)
const missing = targets.filter(target => target.isMissing)
const noneInstalled = (installSucceeded.length === 0)
const allInstalledSuccessfully = (installErrored.length === 0 && missing.length === 0)
const someInstalledSuccessfully = (!noneInstalled && !allInstalledSuccessfully)
summarise(logger, installSkipped, packageNamePrinter, 'The following plugins were skipped:')
summarise(logger, missing, packageNamePrinter, 'There was a problem locating the following plugins:')
summarise(logger, installErrored, errorPrinter, 'The following plugins could not be installed:')
if (noneInstalled) logger?.log(chalk.cyanBright('None of the requested plugins could be installed'))
else if (allInstalledSuccessfully) summarise(logger, installSucceeded, dev ? packageNamePrinter : versionPrinter, 'All requested plugins were successfully installed. Summary of installation:')
else if (someInstalledSuccessfully) summarise(logger, installSucceeded, dev ? packageNamePrinter : versionPrinter, 'The following plugins were successfully installed:')
}
function summarise (logger, list, iterator, header) {
if (!list || !iterator || list.length === 0) return
logger?.log(chalk.cyanBright(header))
list.forEach(item => iterator(item, logger))
}