Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: Use npm for install, update and uninstall where required (fixes #175) #189

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import unregister from './commands/unregister.js'
import update from './commands/update.js'
import version from './commands/version.js'
import logger from './logger.js'
import { isNPM } from './integration/PluginManagement/npm.js'
import './econnreset.js'

const commands = {
Expand Down Expand Up @@ -53,6 +54,7 @@ class CLI {

async execute () {
try {
logger.info(`using ${await isNPM() ? 'NPM' : 'BOWER'} registry...`)
if (!commands[this.command.name]) {
const e = new Error(`Unknown command "${this.command.name}", please check the documentation.`)
logger?.log(e.message)
Expand Down
57 changes: 49 additions & 8 deletions lib/integration/Plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import endpointParser from 'bower-endpoint-parser'
import semver from 'semver'
import fs from 'fs-extra'
import path from 'path'
import { fetchAllInfo, fetchVersionInfo, fetchRepoUrl } from './PluginManagement/npm.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 @@ -57,7 +58,7 @@ export default class Plugin {
const isLocalPath = (isNameAPath || isVersionAPath)
if (isLocalPath) {
// wait to name the plugin until the local config file is loaded
this.sourcePath = isNameAPath ? this.name : this.requestedVersion
this.sourcePath = path.resolve(this.cwd, isNameAPath ? this.name : this.requestedVersion)
this.name = isVersionAPath ? this.packageName : ''
this.packageName = isNameAPath ? '' : this.packageName
this.requestedVersion = '*'
Expand All @@ -79,7 +80,7 @@ export default class Plugin {
* @returns {boolean|null}
*/
get isUpToDate () {
if (!this.hasFrameworkCompatibleVersion) return true;
if (!this.hasFrameworkCompatibleVersion) return true
const canCheckSourceAgainstProject = (this.latestSourceVersion && this.projectVersion)
if (!canCheckSourceAgainstProject) return null
const isLatestVersion = (this.projectVersion === this.latestSourceVersion)
Expand Down Expand Up @@ -178,7 +179,7 @@ export default class Plugin {

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

async fetchLocalSourceInfo () {
Expand All @@ -187,15 +188,20 @@ export default class Plugin {
if (!this.isLocalSource) throw new Error('Plugin name or version must be a path to the source')
if (this.isLocalSourceZip) throw new Error('Cannot install from zip files')
this._sourceInfo = await new Promise((resolve, reject) => {
// get bower.json data
// get package.json or bower.json data
const paths = [
path.resolve(this.cwd, `${this.sourcePath}/package.json`),
path.resolve(this.cwd, `${this.sourcePath}/bower.json`)
]
const bowerJSON = paths.reduce((bowerJSON, bowerJSONPath) => {
if (bowerJSON) return bowerJSON
if (!fs.existsSync(bowerJSONPath)) return null
return fs.readJSONSync(bowerJSONPath)
}, null)
const hasPackageJSON = fs.existsSync(paths[0])
if (this.project.isNPM && !hasPackageJSON) {
fs.copySync(paths[1], paths[0])
}
resolve(bowerJSON)
})
if (!this._sourceInfo) return
Expand All @@ -204,12 +210,20 @@ export default class Plugin {
this.packageName = this.name
}

async fetchBowerInfo () {
async fetchRegistryInfo () {
if (this._sourceInfo) return this._sourceInfo
this._sourceInfo = null
if (this.isLocalSource) return
const isNPM = await this.project.isNPM()
const perform = async (attemptCount = 0) => {
try {
if (isNPM) {
return await fetchAllInfo({
logger: this.logger,
cwd: this.cwd,
packageName: this.packageName
})
}
return await new Promise((resolve, reject) => {
bower.commands.info(`${this.packageName}`, null, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
.on('end', resolve)
Expand All @@ -227,12 +241,19 @@ export default class Plugin {
this._versionsInfo = info.versions.filter(version => semverOptions.includePrerelease ? true : !semver.prerelease(version))
}

async refetchProjectInfo () {
this._projectInfo = null
return this.fetchProjectInfo()
}

async fetchProjectInfo () {
if (this._projectInfo) return this._projectInfo
this._projectInfo = null
this._projectInfo = await new Promise((resolve, reject) => {
// get bower.json data
// get package.json or bower.json data
globs([
`${this.cwd.replace(/\\/g, '/')}/src/node_modules/${this.packageName}/.package.json`,
`${this.cwd.replace(/\\/g, '/')}/src/node_modules/${this.packageName}/package.json`,
`${this.cwd.replace(/\\/g, '/')}/src/*/${this.packageName}/.bower.json`,
`${this.cwd.replace(/\\/g, '/')}/src/*/${this.packageName}/bower.json`
], (err, matches) => {
Expand All @@ -242,8 +263,10 @@ export default class Plugin {
if (!match) {
// widen the search
globs([
`${this.cwd.replace(/\\/g, '/')}/src/**/.bower.json`,
`${this.cwd.replace(/\\/g, '/')}/src/**/bower.json`
`${this.cwd.replace(/\\/g, '/')}/src/node_modules/adapt-*/.package.json`,
`${this.cwd.replace(/\\/g, '/')}/src/node_modules/adapt-*/package.json`,
`${this.cwd.replace(/\\/g, '/')}/src/*/adapt-*/.bower.json`,
`${this.cwd.replace(/\\/g, '/')}/src/*/adapt-*/bower.json`
], (err, matches) => {
if (err) return resolve(null)
const tester = new RegExp(`/${this.packageName}/`, 'i')
Expand All @@ -264,9 +287,18 @@ export default class Plugin {
}

async findCompatibleVersion (framework) {
const isNPM = await this.project.isNPM()
const getBowerVersionInfo = async (version) => {
const perform = async (attemptCount = 0) => {
try {
if (isNPM) {
return await fetchVersionInfo({
logger: this.logger,
cwd: this.cwd,
packageName: this.packageName,
version
})
}
return await new Promise((resolve, reject) => {
bower.commands.info(`${this.packageName}@${version}`, null, { cwd: this.cwd, registry: this.BOWER_REGISTRY_CONFIG })
.on('end', resolve)
Expand Down Expand Up @@ -352,6 +384,15 @@ export default class Plugin {
async getRepositoryUrl () {
if (this._repositoryUrl) return this._repositoryUrl
if (this.isLocalSource) return
const isNPM = await this.project.isNPM()
if (isNPM) {
const url = await fetchRepoUrl({
logger: this.logger,
cwd: this.cwd,
packageName: this.packageName
})
return (this._repositoryUrl = url)
}
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
28 changes: 26 additions & 2 deletions lib/integration/PluginManagement/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Target from '../Target.js'
import bower from 'bower'
import { difference } from 'lodash-es'
import path from 'path'
import { install as npmInstall } from './npm.js'

export default async function install ({
plugins,
Expand All @@ -20,10 +21,15 @@ export default async function install ({
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()

const isNPM = await project.isNPM()

!isNPM && isClean && await new Promise(resolve => {
bower.commands.cache.clean().on('end', resolve)
})

logger?.log(chalk.cyan(`${dev ? 'cloning' : 'installing'} adapt dependencies...`))

const targets = await getInstallTargets({ logger, project, plugins, isCompatibleEnabled })
Expand All @@ -40,8 +46,26 @@ export default async function install ({
await eachOfSeriesProgress(
installTargetsToBeInstalled,
target => target.install({ clone: dev }),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Installing plugins ${percentage}% complete`)
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Installing plugins ${(percentage / (isNPM ? 2 : 1))}% complete`)
)
if (isNPM) {
// Batch install npm plugins as it's faster
const installArgs = installTargetsToBeInstalled
.filter(target => target.isNPMInstall)
.map(target => {
if (target.isLocalSource) {
return `${target.sourcePath}`
}
return `${target.packageName}@${target.versionToApply}`
})
const outputPath = path.join(cwd, 'src')
await npmInstall({ logger, cwd: outputPath, args: installArgs })
await eachOfSeriesProgress(
installTargetsToBeInstalled,
target => target.postInstall(),
percentage => logger?.logProgress?.(`${chalk.bold.cyan('<info>')} Installing plugins ${50 + (percentage / 2)}% complete`)
)
}
logger?.log(`${chalk.bold.cyan('<info>')} Installing plugins 100% complete`)
const manifestDependencies = await project.getManifestDependencies()
await updateManifest({ logger, project, targets, manifestDependencies, isInteractive })
Expand Down
156 changes: 156 additions & 0 deletions lib/integration/PluginManagement/npm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { exec } from 'child_process'
import path from 'path'
import nodeFetch from 'node-fetch'
import getBowerRegistryConfig from '../getBowerRegistryConfig.js'
import semver from 'semver'

const pluginCache = {}
let isNPMCache = null

export async function isNPM ({ cwd = process.cwd() } = {}) {
if (isNPMCache !== null) return isNPMCache
const packageName = 'adapt-contrib-core'
const BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd })
for (const url of BOWER_REGISTRY_CONFIG.search) {
try {
const pluginUrl = `${url}npm/${packageName}`
const req = await nodeFetch(pluginUrl)
const data = await req.json()
isNPMCache = Boolean(typeof data === 'object' && data.name === packageName)
return isNPMCache
} catch (err) {
}
}
return (isNPMCache = false)
}

export async function execute ({
logger,
command,
cwd,
args = []
} = {}) {
cwd = path.resolve(process.cwd(), cwd)
await new Promise((resolve, reject) => {
exec([(process.platform === 'win32' ? 'npm.cmd' : 'npm'), '--unsafe-perm', command, ...args].join(' '), {
cwd
}, (err, stdout, stderr) => {
if (!err) return resolve()
reject(stderr)
})
})
}

export async function install ({
logger,
cwd,
args = []
} = {}) {
await execute({ logger, command: 'install', cwd, args: ['--omit=dev'].concat(args) })
}

export async function update ({
logger,
cwd,
args = []
} = {}) {
await execute({ logger, command: 'update', cwd, args })
}

export async function uninstall ({
logger,
cwd,
args = []
} = {}) {
await execute({ logger, command: 'uninstall', cwd, args })
}

export async function fetchAllInfo ({
logger,
cwd,
packageName
}) {
const BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd })
let json
for (const url of BOWER_REGISTRY_CONFIG.search) {
try {
let data = pluginCache[packageName]
if (!data) {
const pluginUrl = `${url}npm/${packageName}`
const req = await nodeFetch(pluginUrl)
data = await req.json()
}
const versions = Object.values(data.versions).map(item => item.version)
versions.sort((a, b) => semver.compare(a, b) * -1)
json = {
name: data.name,
versions,
latest: data.versions[data['dist-tags'].latest]
}
} catch (err) {
}
}
return json
}

export async function fetchVersionInfo ({
logger,
cwd,
packageName,
version
}) {
const BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd })
for (const url of BOWER_REGISTRY_CONFIG.search) {
try {
let data = pluginCache[packageName]
if (!data) {
const pluginUrl = `${url}npm/${packageName}`
const req = await nodeFetch(pluginUrl)
data = await req.json()
}
return data.versions[version]
} catch (err) {
}
}
return []
}

export async function fetchRepoUrl ({
logger,
cwd,
packageName,
version
}) {
const BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd })
for (const url of BOWER_REGISTRY_CONFIG.search) {
try {
let data = pluginCache[packageName]
if (!data) {
const pluginUrl = `${url}npm/${packageName}`
const req = await nodeFetch(pluginUrl)
data = await req.json()
}
return data.repository
} catch (err) {
}
}
return null
}

export async function searchInfo ({
logger,
cwd,
term
}) {
const BOWER_REGISTRY_CONFIG = getBowerRegistryConfig({ cwd })
for (const url of BOWER_REGISTRY_CONFIG.search) {
try {
const pluginUrl = `${url}npm/-/v1/search?text=${term}&size=100`
const req = await nodeFetch(pluginUrl)
const data = await req.json()
return data?.objects ?? []
} catch (err) {
}
}
return []
}
Loading