Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ class API {
} = {}) {
// cache state of plugins, as these will be wiped
const plugins = (await new Project({ cwd }).getInstallTargets())
.map(p => p.isLocalSource ? p.sourcePath : `${p.name}@${p.requestedVersion}`)
.map(p => {
if (p.isLocalSource) return p.sourcePath
if (p.isGitSource) return p.gitUrl + (p.gitRef ? '#' + p.gitRef : '')
return `${p.name}@${p.requestedVersion}`
})

await this.installFramework({ version, repository, cwd, logger })
// restore plugins
Expand Down Expand Up @@ -271,7 +275,7 @@ class API {
.filter(Boolean)
await async.eachOfLimit(filteredPlugins, 8, async plugin => {
await plugin.fetchProjectInfo()
await plugin.fetchBowerInfo()
await plugin.fetchSourceInfo()
await plugin.findCompatibleVersion(frameworkVersion)
})
return filteredPlugins
Expand Down
13 changes: 2 additions & 11 deletions lib/integration/AdaptFramework/clone.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import chalk from 'chalk'
import { exec } from 'child_process'
import { ADAPT_FRAMEWORK } from '../../util/constants.js'
import path from 'path'
import gitClone from '../../util/gitClone.js'

export default async function clone ({
repository = ADAPT_FRAMEWORK,
Expand All @@ -13,15 +13,6 @@ export default async function clone ({
cwd = path.resolve(process.cwd(), cwd)
if (!branch && !repository) throw new Error('Repository details are required.')
logger?.write(chalk.cyan('cloning framework to', cwd, '\t'))
await new Promise(function (resolve, reject) {
const child = exec(`git clone ${repository} "${cwd}"`)
child.addListener('error', reject)
child.addListener('exit', resolve)
})
await new Promise(function (resolve, reject) {
const child = exec(`git checkout ${branch}`)
child.addListener('error', reject)
child.addListener('exit', resolve)
})
await gitClone({ url: repository, dir: cwd, branch })
logger?.log(' ', 'done!')
}
58 changes: 50 additions & 8 deletions lib/integration/Plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import endpointParser from 'bower-endpoint-parser'
import semver from 'semver'
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import gitClone from '../util/gitClone.js'
import getBowerRegistryConfig from './getBowerRegistryConfig.js'
import { ADAPT_ALLOW_PRERELEASE, PLUGIN_TYPES, PLUGIN_TYPE_FOLDERS, PLUGIN_DEFAULT_TYPE } from '../util/constants.js'
/** @typedef {import("./Project.js").default} Project */
Expand Down Expand Up @@ -38,13 +40,23 @@ export default class Plugin {
this.project = project
this.cwd = cwd
this.BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd: this.cwd })
const endpoint = name + '#' + (isCompatibleEnabled ? '*' : requestedVersion)
const ep = endpointParser.decompose(endpoint)
this.sourcePath = null
this.name = ep.name || ep.source
this.packageName = (/^adapt-/i.test(this.name) ? '' : 'adapt-') + (!isContrib ? '' : 'contrib-') + slug(this.name, { maintainCase: true })
// the constraint given by the user
this.requestedVersion = requestedVersion

const isGitUrl = /^https?:\/\//.test(name)
if (isGitUrl) {
this.gitUrl = name
this.gitRef = (requestedVersion && requestedVersion !== '*') ? requestedVersion : null
this.name = ''
this.packageName = ''
this.requestedVersion = '*'
} else {
const endpoint = name + '#' + (isCompatibleEnabled ? '*' : requestedVersion)
const ep = endpointParser.decompose(endpoint)
this.name = ep.name || ep.source
this.packageName = (/^adapt-/i.test(this.name) ? '' : 'adapt-') + (!isContrib ? '' : 'contrib-') + slug(this.name, { maintainCase: true })
this.requestedVersion = requestedVersion
}

// the most recent version of the plugin compatible with the given framework
this.latestCompatibleSourceVersion = null
// a non-wildcard constraint resolved to the highest version of the plugin that satisfies the requestedVersion and is compatible with the framework
Expand Down Expand Up @@ -128,6 +140,14 @@ export default class Plugin {
return Boolean(this.sourcePath || this?._projectInfo?._wasInstalledFromPath)
}

/**
* plugin will be or was installed from a git URL
* @returns {boolean}
*/
get isGitSource () {
return Boolean(this.gitUrl || this._projectInfo?._wasInstalledFromGitRepo)
}

/**
* check if source path is a zip
* @returns {boolean}
Expand Down Expand Up @@ -185,10 +205,28 @@ export default class Plugin {
}

async fetchSourceInfo () {
if (this.isGitSource) return await this.fetchGitSourceInfo()
if (this.isLocalSource) return await this.fetchLocalSourceInfo()
await this.fetchBowerInfo()
}

async fetchGitSourceInfo () {
if (this._sourceInfo) return this._sourceInfo
this._sourceInfo = null
const tmpDir = path.join(os.tmpdir(), `adapt-git-${Date.now()}`)
try {
await gitClone({ url: this.gitUrl, dir: tmpDir, branch: this.gitRef, shallow: true })
const bowerJSONPath = path.join(tmpDir, 'bower.json')
if (!fs.existsSync(bowerJSONPath)) return
this._sourceInfo = await fs.readJSON(bowerJSONPath)
this.name = this._sourceInfo.name
this.packageName = this.name
this.matchedVersion = this._sourceInfo.version
} finally {
await fs.rm(tmpDir, { recursive: true, force: true })
}
}

async fetchLocalSourceInfo () {
if (this._sourceInfo) return this._sourceInfo
this._sourceInfo = null
Expand Down Expand Up @@ -269,6 +307,10 @@ export default class Plugin {
if (!this._projectInfo) return
this.name = this._projectInfo.name
this.packageName = this.name
if (this._projectInfo._wasInstalledFromGitRepo) {
this.gitUrl = this._projectInfo._gitUrl
this.gitRef = this._projectInfo._gitRef || null
}
}

async findCompatibleVersion (framework) {
Expand All @@ -291,7 +333,7 @@ export default class Plugin {
const getMatchingVersion = async () => {
if (!this.isPresent) return null

if (this.isLocalSource) {
if (this.isLocalSource || this.isGitSource) {
const info = this.projectVersion ? this._projectInfo : this._sourceInfo
const satisfiesConstraint = !this.hasValidRequestVersion || semver.satisfies(info.version, this.requestedVersion, semverOptions)
const satisfiesFramework = semver.satisfies(framework, info.framework)
Expand Down Expand Up @@ -360,7 +402,7 @@ export default class Plugin {

async getRepositoryUrl () {
if (this._repositoryUrl) return this._repositoryUrl
if (this.isLocalSource) return
if (this.isLocalSource || this.isGitSource) return
const url = await new Promise((resolve, reject) => {
bower.commands.lookup(this.packageName, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
.on('end', resolve)
Expand Down
27 changes: 27 additions & 0 deletions lib/integration/PluginManagement/clone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import fs from 'fs-extra'
import path from 'path'
import gitClone from '../../util/gitClone.js'

/**
* Clone a plugin from a git URL and write .bower.json metadata
* @param {Object} options
* @param {string} options.url The git repository URL
* @param {string} options.destPath The target directory for the plugin
* @param {string} [options.branch] Optional branch, tag, or ref
* @returns {Object} The bower.json contents (with git metadata)
*/
export default async function clonePlugin ({
url,
destPath,
branch = null
} = {}) {
await fs.ensureDir(path.dirname(destPath))
await fs.rm(destPath, { recursive: true, force: true })
await gitClone({ url, dir: destPath, branch })
const bowerJSON = await fs.readJSON(path.join(destPath, 'bower.json'))
bowerJSON._gitUrl = url
bowerJSON._gitRef = branch || undefined
bowerJSON._wasInstalledFromGitRepo = true
await fs.writeJSON(path.join(destPath, '.bower.json'), bowerJSON, { spaces: 2, replacer: null })
return bowerJSON
}
21 changes: 15 additions & 6 deletions lib/integration/PluginManagement/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,28 @@ async function getInstallTargets ({ logger, project, plugins, isCompatibleEnable
const itinerary = isEmpty
? await project.getManifestDependencies()
: plugins.reduce((itinerary, arg) => {
const [name, version = '*'] = arg.split(/[#@]/)
let name, version
if (/^https?:\/\//.test(arg)) {
const hashIndex = arg.lastIndexOf('#')
if (hashIndex !== -1) {
name = arg.substring(0, hashIndex)
version = arg.substring(hashIndex + 1)
} else {
name = arg
version = '*'
}
} else {
[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(/[#@]/)
const targets = Object.keys(itinerary).length
? Object.entries(itinerary).map(([name, requestedVersion]) => {
return new Target({ name, requestedVersion, isCompatibleEnabled, project, logger })
})
: await project.getInstallTargets()
Expand Down
6 changes: 4 additions & 2 deletions lib/integration/PluginManagement/print.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ export function versionPrinter (plugin, logger) {
versionToApply,
latestCompatibleSourceVersion
} = plugin
const sourceLabel = plugin.isLocalSource ? ' (local)' : plugin.isGitSource ? ' (git)' : ` (latest compatible version is ${greenIfEqual(versionToApply, latestCompatibleSourceVersion)})`
logger?.log(highlight(plugin.packageName), latestCompatibleSourceVersion === null
? '(no version information)'
: `${chalk.greenBright(versionToApply)}${plugin.isLocalSource ? ' (local)' : ` (latest compatible version is ${greenIfEqual(versionToApply, latestCompatibleSourceVersion)})`}`
: `${chalk.greenBright(versionToApply)}${sourceLabel}`
)
}

Expand All @@ -37,9 +38,10 @@ export function existingVersionPrinter (plugin, logger) {
const fromTo = preUpdateProjectVersion !== null
? `from ${chalk.greenBright(preUpdateProjectVersion)} to ${chalk.greenBright(projectVersion)}`
: `${chalk.greenBright(projectVersion)}`
const sourceLabel = plugin.isLocalSource ? ' (local)' : plugin.isGitSource ? ' (git)' : ` (latest compatible version is ${greenIfEqual(projectVersion, latestCompatibleSourceVersion)})`
logger?.log(highlight(plugin.packageName), latestCompatibleSourceVersion === null
? fromTo
: `${fromTo}${plugin.isLocalSource ? ' (local)' : ` (latest compatible version is ${greenIfEqual(projectVersion, latestCompatibleSourceVersion)})`}`
: `${fromTo}${sourceLabel}`
)
}

Expand Down
15 changes: 9 additions & 6 deletions lib/integration/PluginManagement/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ async function conflictResolution ({ logger, targets, isInteractive }) {
prompt
}
}
const preFilteredPlugins = targets.filter(target => !target.isLocalSource)
const preFilteredPlugins = targets.filter(target => !target.isLocalSource && !target.isGitSource)
const allQuestions = [
add(preFilteredPlugins.filter(target => !target.hasFrameworkCompatibleVersion && target.latestSourceVersion), 'There is no compatible version of the following plugins:', checkVersion),
add(preFilteredPlugins.filter(target => target.hasFrameworkCompatibleVersion && !target.hasValidRequestVersion), 'The version requested is invalid, there are newer compatible versions of the following plugins:', checkVersion),
Expand All @@ -181,28 +181,31 @@ async function conflictResolution ({ logger, targets, isInteractive }) {
* @param {[Target]} options.targets
*/
function summariseDryRun ({ logger, targets }) {
const preFilteredPlugins = targets.filter(target => !target.isLocalSource)
const preFilteredPlugins = targets.filter(target => !target.isLocalSource && !target.isGitSource)
const localSources = targets.filter(target => target.isLocalSource)
const gitSources = targets.filter(target => target.isGitSource && target.isToBeUpdated)
const toBeInstalled = preFilteredPlugins.filter(target => target.isToBeUpdated)
const toBeSkipped = preFilteredPlugins.filter(target => !target.isToBeUpdated || target.isSkipped)
const missing = preFilteredPlugins.filter(target => target.isMissing)
summarise(logger, localSources, packageNamePrinter, 'The following plugins were installed from a local source and cannot be updated:')
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, existingVersionPrinter, 'The following plugins will be updated:')
summarise(logger, [...toBeInstalled, ...gitSources], existingVersionPrinter, 'The following plugins will be updated:')
}

/**
* @param {Object} options
* @param {[Target]} options.targets
*/
function summariseUpdates ({ logger, targets }) {
const preFilteredPlugins = targets.filter(target => !target.isLocalSource)
const preFilteredPlugins = targets.filter(target => !target.isLocalSource && !target.isGitSource)
const localSources = targets.filter(target => target.isLocalSource)
const installSucceeded = preFilteredPlugins.filter(target => target.isUpdateSuccessful)
const gitSucceeded = targets.filter(target => target.isGitSource && target.isUpdateSuccessful)
const gitErrored = targets.filter(target => target.isGitSource && target.isUpdateFailure)
const installSucceeded = [...preFilteredPlugins.filter(target => target.isUpdateSuccessful), ...gitSucceeded]
const installSkipped = preFilteredPlugins.filter(target => target.isSkipped)
const noUpdateAvailable = preFilteredPlugins.filter(target => !target.isToBeUpdated && !target.isSkipped)
const installErrored = preFilteredPlugins.filter(target => target.isUpdateFailure)
const installErrored = [...preFilteredPlugins.filter(target => target.isUpdateFailure), ...gitErrored]
const missing = preFilteredPlugins.filter(target => target.isMissing)
const noneInstalled = (installSucceeded.length === 0)
const allInstalledSuccessfully = (installErrored.length === 0 && missing.length === 0)
Expand Down
15 changes: 13 additions & 2 deletions lib/integration/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ export default class Project {

/** @returns {[Target]} */
async getInstallTargets () {
return Object.entries(await this.getManifestDependencies()).map(([name, requestedVersion]) => new Target({ name, requestedVersion, project: this, logger: this.logger }))
return Object.entries(await this.getManifestDependencies()).map(([name, requestedVersion]) => {
if (/^https?:\/\//.test(requestedVersion)) {
const hashIndex = requestedVersion.lastIndexOf('#')
if (hashIndex !== -1) {
return new Target({ name: requestedVersion.substring(0, hashIndex), requestedVersion: requestedVersion.substring(hashIndex + 1), project: this, logger: this.logger })
}
return new Target({ name: requestedVersion, project: this, logger: this.logger })
}
return new Target({ name, requestedVersion, project: this, logger: this.logger })
})
}

/** @returns {[string]} */
Expand Down Expand Up @@ -128,7 +137,9 @@ export default class Project {
if (this.containsManifestFile) {
manifest = readValidateJSONSync(this.manifestFilePath)
}
manifest.dependencies[plugin.packageName] = plugin.sourcePath || plugin.requestedVersion || plugin.version
manifest.dependencies[plugin.packageName] = plugin.gitUrl
? (plugin.gitUrl + (plugin.gitRef ? '#' + plugin.gitRef : ''))
: plugin.sourcePath || plugin.requestedVersion || plugin.version
fs.writeJSONSync(this.manifestFilePath, manifest, { spaces: 2, replacer: null })
}

Expand Down
Loading