UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

385 lines (351 loc) 13.1 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2017-2021 The authors License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * John Spackman (john.spackman@zenesis.com, @johnspackman) * Christian Boulanger (info@bibliograph.org, @cboulanger) ************************************************************************ */ const fs = qx.tool.utils.Promisify.fs; const process = require("process"); const path = require("upath"); const semver = require("semver"); /** * NOTE: some of the names of the methods in this class do not express very clearly * what they do and might be renamed before 7.0.0 */ qx.Class.define("qx.tool.config.Utils", { type: "static", statics: { /** @type{Promise<String} promise for cache of getQxPath() */ __qxPathPromise: null, /** * Returns data on the project in the currect working directory. * If a qooxdoo.json file exists, the data is taken from there. * If not, the relies on the following assumptions: * * 1. If a Manifest.json exists in the current working directory, * it is assumed to be the main library directory. * * 2. If a compile.json file exists in the current working directory, * it is assumed to be the directory in which the application can be found. * * The method returns a promise that resolves to a map containing the following keys: * 'libraries': an array of maps containing a 'path' property with a relative path to a library folder, * 'applications': an array of maps containing a 'path' property with a relative path to an * application folder. * * If no libraries or applications can be found, empty arrays are returned. * * @param {String?} dir The base directory. If not given, the current working dir is used * @return {Promise<Object>} */ async getProjectData(dir = null) { dir = dir || process.cwd(); let qooxdooJsonPath = path.join( dir, qx.tool.config.Registry.config.fileName ); let data = { libraries: [], applications: [] }; if (await fs.existsAsync(qooxdooJsonPath)) { let qooxdooJson = await qx.tool.utils.Json.loadJsonAsync( qooxdooJsonPath ); if (qx.lang.Type.isArray(qooxdooJson.libraries)) { data.libraries = qooxdooJson.libraries; } if (qx.lang.Type.isArray(qooxdooJson.applications)) { data.applications = qooxdooJson.applications; } } if ( await fs.existsAsync( path.join(dir, qx.tool.config.Manifest.config.fileName) ) ) { if (!data.libraries.find(lib => lib.path === ".")) { data.libraries.push({ path: "." }); } } if ( await fs.existsAsync( path.join(dir, qx.tool.config.Compile.config.fileName) ) ) { if (!data.applications.find(app => app.path === ".")) { data.applications.push({ path: "." }); } } return data; }, /** * Returns the path to the library in the current working directory. If that * directory contains several libraries, the first one found is returned. * * @param {String?} dir The base directory. If not given, the current working dir is used * @throws {Error} Throws an error if no library can be found. * @return {String} A promise that resolves with the absolute path to the library */ async getLibraryPath(dir = null) { dir = dir || process.cwd(); let { libraries } = await this.getProjectData(dir); if (libraries instanceof Array && libraries.length) { return path.resolve(process.cwd(), libraries[0].path); } throw new qx.tool.utils.Utils.UserError( "Cannot find library path - are you in the right directory?" ); }, /** * Returns the path to the current application, depending on * the current working directory. If a directory contains * several applications, the first one found is returned. * * @param {String?} dir The base directory. If not given, the current working dir is used * @throws {Error} Throws an error if no application can be found. * @return {Promise<String>} A promise that resolves with the absolute path to the application */ async getApplicationPath(dir = null) { dir = dir || process.cwd(); let { applications } = await this.getProjectData(dir); if (applications instanceof Array && applications.length) { return path.resolve(process.cwd(), applications[0].path); } throw new qx.tool.utils.Utils.UserError( "Cannot find application path - are you in the right directory?" ); }, /** * Compute the path to the qooxdoo library (the `qx` namespace) * which is used independently of the application being compiled. * * The path will be resolved via the following strategies: * * 1. finding a `Manifest.json` in the current working directory that provides * the `qx` library, or such a file in the parent directory, its parent dir, * etc., up to the root. * * 2. The qx library contained in the projects `node_modules` folder, if it exists, * or in the parent directory's, etc. * * 3. if not found try 1. and 2. with current script dir * * 4. A globally installed `@qooxdoo/framework` NPM package. * * If all strategies fail, an error is thrown. * * @param {String?} dir The base directory. If not given, the current working dir is used * @return {Promise<string>} */ async getQxPath() { if (this.__qxPathPromise) { return await this.__qxPathPromise; } const scanAncestors = async dir => { let root = path.parse(dir).root; while (dir !== root) { // 1. Manifest.json files if (await this.isQxLibrary(dir)) { return dir; } // 2. node_modules folders let npmdir = path.join(dir, "node_modules", "@qooxdoo", "framework"); if (await this.isQxLibrary(npmdir)) { return npmdir; } // walk up the directory tree dir = path.resolve(path.join(dir, "..")); } return null; }; const getQxPathImpl = async () => { // 1. Look for the parent directory of the currently running command (eg `qx`) let res = await scanAncestors(path.parse(require.main.filename).dir); if (res) { return res; } // 2. current dir res = await scanAncestors(path.resolve(process.cwd())); if (res) { return res; } // 3. try script dir /* eslint-disable-next-line @qooxdoo/qx/no-illegal-private-usage */ res = await scanAncestors(__dirname); if (res) { return res; } // 4. global npm package let npmdir = (await qx.tool.utils.Utils.exec("npm root -g")).trim(); res = path.join(npmdir, "@qooxdoo", "framework"); if (await this.isQxLibrary(res)) { return res; } throw new qx.tool.utils.Utils.UserError( `Path to the qx library cannot be determined.` ); }; this.__qxPathPromise = getQxPathImpl(); return await this.__qxPathPromise; }, /** * Returns true if a compilable application exists in the given directory by checking * if there is a "compile.json" file. * * @param {String?} dir The base directory. If not given, the current working dir is used * @return {Promise<Boolean>} */ async applicationExists(dir) { return await fs.existsAsync( path.join(dir, qx.tool.config.Compile.config.fileName) ); }, /** * Returns the qooxdoo version from the current environment (not the application) * @param {String?} dir The base directory. If not given, the current working dir is used * @return {Promise<String>} */ async getQxVersion() { let qxpath = await this.getQxPath(); return qx.tool.config.Utils.getLibraryVersion(qxpath); }, /** * returns the compiler version. * The version is written during compiler compile into the enviroment * @return {String} */ getCompilerVersion() { return qx.core.Environment.get("qx.compiler.version"); }, /** * Returns the qooxdoo version used in the application in the current or given * directory. Throws if no such version can be determined * * @param {String?} baseDir The base directory. If not given, the current working dir is used * @return {Promise<String>} */ async getAppQxVersion(baseDir = null) { baseDir = baseDir || process.cwd(); let manifestRequiresKey = "@qooxdoo/framework"; let manifestModel = await qx.tool.config.Manifest.getInstance() .set({ baseDir, warnOnly: true, validate: false }) .load(); let qxVersion; let qxVersionRange = manifestModel.getValue( `requires.${manifestRequiresKey}` ); qx.log.Logger.debug( `Manifest in ${baseDir} requires ${manifestRequiresKey}: ${qxVersionRange}` ); if (qxVersionRange && !qxVersionRange.match(/[<>]/)) { // cannot do comparisons try { // get the highest version mentioned with a tilde or caret range qxVersion = qxVersionRange .match(/[\^~]?([-0-9a-z._]+)/g) .sort() .reverse()[0] .slice(1); } catch (e) {} } if (!qxVersion || !semver.valid(qxVersion)) { throw new Error( `Cannot determine the qooxdoo version used to compile the application. ` + `Please specify a caret or tilde range for the requires.${manifestRequiresKey} key in the Manifest")` ); } return qxVersion; }, /** * Returns true if the library in the given path provides the "qx" library * @param {String} libraryPath * @return {Promise<boolean>} */ async isQxLibrary(libraryPath) { let manifestPath = path.join( libraryPath, qx.tool.config.Manifest.config.fileName ); if (!(await fs.existsAsync(manifestPath))) { return false; } try { let manifest = await qx.tool.utils.Json.loadJsonAsync(manifestPath); if (manifest.provides && manifest.provides.namespace === "qx") { return true; } } catch (e) { throw new qx.tool.utils.Utils.UserError( `Invalid manifest file ${manifestPath}.` ); } return false; }, /** * Returns an array of {@link qx.tool.config.Abstract} Objects which contain * metadata on the `Manifest.json` file(s) in the current project/package. * @param {String?} cwd The working directory. If not given, the current working dir is used * @return {Promise<qx.tool.config.Manifest[]>} */ async getManifestModels(cwd = null) { cwd = cwd || process.cwd(); const registryModel = qx.tool.config.Registry.getInstance(); let manifestModels = []; if (await registryModel.exists()) { // we have a qooxdoo.json index file containing the paths of libraries in the repository await registryModel.load(); let libraries = registryModel.getLibraries(); for (let library of libraries) { manifestModels.push( new qx.tool.config.Abstract(qx.tool.config.Manifest.config).set({ baseDir: path.join(cwd, library.path) }) ); } } else if ( await fs.existsAsync(qx.tool.config.Manifest.config.fileName) ) { manifestModels.push(qx.tool.config.Manifest.getInstance()); } return manifestModels; }, /** * Given the path to a library folder, returns the library version from its manifest * @param {String} libPath * @return {Promise<String>} Version */ async getLibraryVersion(libPath) { let manifestPath = path.join( libPath, qx.tool.config.Manifest.config.fileName ); let manifest = await qx.tool.utils.Json.loadJsonAsync(manifestPath); if (!manifest) { throw new Error(`No Manifest exists at ${manifestPath}.`); } let version; try { version = manifest.info.version; } catch (e) { throw new Error(`No valid version data in ${manifestPath}.`); } if (!semver.valid(version)) { throw new qx.tool.utils.Utils.UserError( `Manifest at ${manifestPath} contains invalid version number "${version}". Please use a semver compatible version.` ); } return version; } } });