From 8c79a159e5c259017f3ca438da96bce80d56834e Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Wed, 25 Mar 2026 18:29:11 -0600 Subject: [PATCH 1/5] Fix: Prevent plugins from being installed to wrong type directory (fixes #235) --- lib/integration/Plugin.js | 16 ++++++++++++++++ lib/integration/PluginManagement/install.js | 6 ++++++ lib/integration/Target.js | 1 + 3 files changed, 23 insertions(+) diff --git a/lib/integration/Plugin.js b/lib/integration/Plugin.js index c35ed2c..399b333 100644 --- a/lib/integration/Plugin.js +++ b/lib/integration/Plugin.js @@ -335,6 +335,8 @@ export default class Plugin { */ async getType () { if (this._type) return this._type + const pathType = this.getTypeFromProjectPath() + if (pathType) return (this._type = pathType) const info = await this.getInfo() if (!info) return null const foundAttributeType = PLUGIN_TYPES.find(type => info[type]) @@ -347,6 +349,20 @@ export default class Plugin { return (this._type = foundAttributeType || foundKeywordType || PLUGIN_DEFAULT_TYPE) } + /** + * Infer plugin type from the installed project path. + * e.g. src/menu/adapt-contrib-boxMenu → 'menu' + * @returns {string|null} + */ + getTypeFromProjectPath () { + if (!this.projectPath) return null + const folderToType = Object.fromEntries( + Object.entries(PLUGIN_TYPE_FOLDERS).map(([type, folder]) => [folder, type]) + ) + const parentFolder = path.basename(path.dirname(this.projectPath)) + return folderToType[parentFolder] || null + } + async getTypeFolder () { const type = await this.getType() return PLUGIN_TYPE_FOLDERS[type] diff --git a/lib/integration/PluginManagement/install.js b/lib/integration/PluginManagement/install.js index 1098bfd..e955c47 100644 --- a/lib/integration/PluginManagement/install.js +++ b/lib/integration/PluginManagement/install.js @@ -95,6 +95,12 @@ async function loadPluginData ({ logger, project, targets }) { percentage => logger?.logProgress?.(`${chalk.bold.cyan('')} Getting plugin info ${percentage}% complete`) ) logger?.log(`${chalk.bold.cyan('')} Getting plugin info 100% complete`) + await eachOfLimitProgress( + targets, + target => target.fetchProjectInfo(), + percentage => logger?.logProgress?.(`${chalk.bold.cyan('')} Checking installed plugins ${percentage}% complete`) + ) + logger?.log(`${chalk.bold.cyan('')} Checking installed plugins 100% complete`) await eachOfLimitProgress( targets, target => target.findCompatibleVersion(frameworkVersion), diff --git a/lib/integration/Target.js b/lib/integration/Target.js index 5bf271c..ba65a0c 100644 --- a/lib/integration/Target.js +++ b/lib/integration/Target.js @@ -122,6 +122,7 @@ export default class Target extends Plugin { markInstallable () { if (!this.isApplyLatestCompatibleVersion && !(this.isLocalSource && this.latestSourceVersion)) return + if (this.projectVersion === this.matchedVersion) return this.versionToApply = this.matchedVersion } From 419432b50033d06f30b80b02c6fb689771d29b49 Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Mon, 13 Apr 2026 12:18:31 -0600 Subject: [PATCH 2/5] Typo --- lib/integration/Plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/integration/Plugin.js b/lib/integration/Plugin.js index 399b333..990e995 100644 --- a/lib/integration/Plugin.js +++ b/lib/integration/Plugin.js @@ -351,7 +351,7 @@ export default class Plugin { /** * Infer plugin type from the installed project path. - * e.g. src/menu/adapt-contrib-boxMenu → 'menu' + * e.g. src/menu/adapt-contrib-boxMenu -> 'menu' * @returns {string|null} */ getTypeFromProjectPath () { From b1fee66d0c6b89ef137c772dec5d294330172445 Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Thu, 4 Jun 2026 15:47:18 -0600 Subject: [PATCH 3/5] Update: Remove path-based plugin type inference --- lib/integration/Plugin.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/integration/Plugin.js b/lib/integration/Plugin.js index 990e995..c35ed2c 100644 --- a/lib/integration/Plugin.js +++ b/lib/integration/Plugin.js @@ -335,8 +335,6 @@ export default class Plugin { */ async getType () { if (this._type) return this._type - const pathType = this.getTypeFromProjectPath() - if (pathType) return (this._type = pathType) const info = await this.getInfo() if (!info) return null const foundAttributeType = PLUGIN_TYPES.find(type => info[type]) @@ -349,20 +347,6 @@ export default class Plugin { return (this._type = foundAttributeType || foundKeywordType || PLUGIN_DEFAULT_TYPE) } - /** - * Infer plugin type from the installed project path. - * e.g. src/menu/adapt-contrib-boxMenu -> 'menu' - * @returns {string|null} - */ - getTypeFromProjectPath () { - if (!this.projectPath) return null - const folderToType = Object.fromEntries( - Object.entries(PLUGIN_TYPE_FOLDERS).map(([type, folder]) => [folder, type]) - ) - const parentFolder = path.basename(path.dirname(this.projectPath)) - return folderToType[parentFolder] || null - } - async getTypeFolder () { const type = await this.getType() return PLUGIN_TYPE_FOLDERS[type] From b9bb26c0103e1cfcd54ed404a1860b1fd1222a2e Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Thu, 4 Jun 2026 16:02:09 -0600 Subject: [PATCH 4/5] Fix: Isolate bower install cwd so a stray root bower.json cannot duplicate plugins (fixes #235) --- lib/integration/Target.js | 59 ++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/integration/Target.js b/lib/integration/Target.js index ba65a0c..62cc6cc 100644 --- a/lib/integration/Target.js +++ b/lib/integration/Target.js @@ -4,6 +4,7 @@ import { exec } from 'child_process' import semver from 'semver' import fs from 'fs-extra' import path from 'path' +import os from 'os' import { ADAPT_ALLOW_PRERELEASE } from '../util/constants.js' import Plugin from './Plugin.js' /** @typedef {import("./Project.js").default} Project */ @@ -157,6 +158,34 @@ export default class Target extends Plugin { this.versionToApply = this.projectVersion } + /** + * Install a plugin via bower into outputPath. Runs from an isolated working + * directory so an ambient project bower.json cannot inject its own + * dependencies into the install directory (fixes #235). + * @param {string} pluginNameVersion e.g. adapt-contrib-media@7.0.6 + * @param {string} outputPath absolute path to the plugin type folder + */ + async bowerInstall (pluginNameVersion, outputPath) { + const isolatedCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'adapt-cli-')) + try { + await new Promise((resolve, reject) => { + bower.commands.install([pluginNameVersion], null, { + directory: outputPath, + cwd: isolatedCwd, + registry: this.BOWER_REGISTRY_CONFIG + }) + .on('end', resolve) + .on('error', err => { + err = new Error(`Bower reported ${err}`) + this._error = err + reject(err) + }) + }) + } finally { + await fs.rm(isolatedCwd, { recursive: true, force: true }) + } + } + async install ({ clone = false } = {}) { const logger = this.logger const pluginTypeFolder = await this.getTypeFolder() @@ -213,20 +242,7 @@ export default class Target extends Plugin { } catch (err) { throw new Error(`There was a problem writing to the target directory ${pluginPath}`) } - await new Promise((resolve, reject) => { - const pluginNameVersion = `${this.packageName}@${this.versionToApply}` - bower.commands.install([pluginNameVersion], null, { - directory: outputPath, - cwd: this.cwd, - registry: this.BOWER_REGISTRY_CONFIG - }) - .on('end', resolve) - .on('error', err => { - err = new Error(`Bower reported ${err}`) - this._error = err - reject(err) - }) - }) + await this.bowerInstall(`${this.packageName}@${this.versionToApply}`, outputPath) const bowerJSON = await fs.readJSON(path.join(pluginPath, 'bower.json')) bowerJSON.version = bowerJSON.version ?? this.versionToApply; await fs.writeJSON(path.join(pluginPath, '.bower.json'), bowerJSON, { spaces: 2, replacer: null }) @@ -244,20 +260,7 @@ export default class Target extends Plugin { } catch (err) { throw new Error(`There was a problem writing to the target directory ${pluginPath}`) } - await new Promise((resolve, reject) => { - const pluginNameVersion = `${this.packageName}@${this.matchedVersion}` - bower.commands.install([pluginNameVersion], null, { - directory: outputPath, - cwd: this.cwd, - registry: this.BOWER_REGISTRY_CONFIG - }) - .on('end', resolve) - .on('error', err => { - err = new Error(`Bower reported ${err}`) - this._error = err - reject(err) - }) - }) + await this.bowerInstall(`${this.packageName}@${this.matchedVersion}`, outputPath) this.preUpdateProjectVersion = this.projectVersion this._projectInfo = null await this.fetchProjectInfo() From c6dabcb6e4b310c722aed4cdbe0803c6b90e320f Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Thu, 4 Jun 2026 16:19:24 -0600 Subject: [PATCH 5/5] Fix: Preserve project .bowerrc when isolating bower install cwd --- lib/integration/Target.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/integration/Target.js b/lib/integration/Target.js index 62cc6cc..9e73120 100644 --- a/lib/integration/Target.js +++ b/lib/integration/Target.js @@ -161,12 +161,17 @@ export default class Target extends Plugin { /** * Install a plugin via bower into outputPath. Runs from an isolated working * directory so an ambient project bower.json cannot inject its own - * dependencies into the install directory (fixes #235). + * dependencies into the install directory (fixes #235). The project .bowerrc + * is copied across so registry, proxy and other bower settings are preserved. * @param {string} pluginNameVersion e.g. adapt-contrib-media@7.0.6 * @param {string} outputPath absolute path to the plugin type folder */ async bowerInstall (pluginNameVersion, outputPath) { const isolatedCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'adapt-cli-')) + const bowerrcPath = path.join(this.cwd, '.bowerrc') + if (await fs.pathExists(bowerrcPath)) { + await fs.copy(bowerrcPath, path.join(isolatedCwd, '.bowerrc')) + } try { await new Promise((resolve, reject) => { bower.commands.install([pluginNameVersion], null, {