diff --git a/src/check-for-blueprint-updates.js b/src/check-for-blueprint-updates.js index 3f2affb3..dff66ec7 100644 --- a/src/check-for-blueprint-updates.js +++ b/src/check-for-blueprint-updates.js @@ -6,34 +6,44 @@ const getTagVersion = require('boilerplate-update/src/get-tag-version'); const { defaultTo } = require('./constants'); const utils = require('./utils'); +/** + * + * @param {Object} options + * @param {string} options.cwd + * @param {string} options.blueprintName + * @returns {Promise} + */ +async function getLatestVersion({ cwd, blueprint }) { + if (blueprint.location) { + let parsedPackage = await parseBlueprintPackage({ + cwd, + packageName: blueprint.location || blueprint.packageName + }); + + let downloadedPackage = await downloadPackage( + blueprint.packageName, + parsedPackage.url, + defaultTo + ); + + return downloadedPackage.version; + } + + let versions = await utils.getVersions(blueprint.packageName); + let latestVersion = await getTagVersion({ + range: defaultTo, + versions, + packageName: blueprint.packageName + }); + + return latestVersion; +} + async function checkForBlueprintUpdates({ cwd, blueprints }) { return await Promise.all( blueprints.map(async blueprint => { let currentVersion = blueprint.version; - let latestVersion; - - if (!blueprint.location) { - let versions = await utils.getVersions(blueprint.packageName); - - latestVersion = await getTagVersion({ - range: defaultTo, - versions, - packageName: blueprint.packageName - }); - } else { - let parsedPackage = await parseBlueprintPackage({ - cwd, - packageName: blueprint.location || blueprint.packageName - }); - - let downloadedPackage = await downloadPackage( - blueprint.packageName, - parsedPackage.url, - defaultTo - ); - - latestVersion = downloadedPackage.version; - } + let latestVersion = await getLatestVersion({ cwd, blueprint }); return { blueprint, diff --git a/src/choose-blueprint-updates.js b/src/choose-blueprint-updates.js index 4895380e..b3bc03d2 100644 --- a/src/choose-blueprint-updates.js +++ b/src/choose-blueprint-updates.js @@ -4,6 +4,7 @@ const checkForBlueprintUpdates = require('./check-for-blueprint-updates'); const inquirer = require('inquirer'); const loadSafeBlueprint = require('./load-safe-blueprint'); const { defaultTo } = require('./constants'); +const semver = require('semver'); /** * Format the string that is displayed when user is prompted for a blueprint @@ -34,10 +35,10 @@ async function chooseBlueprint({ choicesByName, message }) { * * @param {string} cwd - Used in `checkForBlueprintUpdates` in order to generate url or path to it if on local disk * @param {object} emberCliUpdateJson - Use the `blueprints` property from this - * @param {boolean} reset - Optional - * @param {boolean} compare - Optional - * @param {boolean} codemods - Optional - * @param {string} to - Optional (could be undefined). + * @param {boolean} [reset] + * @param {boolean} [compare] + * @param {boolean} [codemods] + * @param {string} [to] - Optional (could be undefined). * @returns {Promise<{blueprint: (*|{}), areAllUpToDate, to: string}>} */ async function chooseBlueprintUpdates({ @@ -50,28 +51,28 @@ async function chooseBlueprintUpdates({ }) { let existingBlueprint; let areAllUpToDate; - let { blueprints } = emberCliUpdateJson; if (reset || compare || codemods) { let choicesByName = blueprints.reduce((choices, blueprint) => { let name = blueprint.packageName; + choices[name] = { blueprint, choice: { name } }; + return choices; }, {}); - let message; + let message = 'Which blueprint would you like to run codemods for?'; + if (reset) { message = 'Which blueprint would you like to reset?'; } else if (compare) { message = 'Which blueprint would you like to compare?'; - } else { - message = 'Which blueprint would you like to run codemods for?'; } existingBlueprint = ( @@ -90,22 +91,35 @@ async function chooseBlueprintUpdates({ blueprintUpdate => blueprintUpdate.isUpToDate ); - if (areAllUpToDate) { - console.log(`${blueprintUpdates.map(formatBlueprintLine).join(` -`)} - -All blueprints are up-to-date!`); + if (!to && areAllUpToDate) { + console.log( + `${blueprintUpdates.map(formatBlueprintLine).join('\n')}\n\nAll blueprints are up-to-date!` + ); } else { + areAllUpToDate = false; + + for (let update of blueprintUpdates) { + if ( + update.blueprint.isBaseBlueprint && + !!to && + semver.gt(to, update.latestVersion) + ) { + update.isUpToDate = false; + update.latestVersion = to; + } + } + let choicesByName = blueprintUpdates.reduce( (choices, blueprintUpdate) => { let name = formatBlueprintLine(blueprintUpdate); + choices[name] = { blueprintUpdate, choice: { - name, - disabled: blueprintUpdate.isUpToDate + name } }; + return choices; }, {} diff --git a/src/constants.js b/src/constants.js index 445f9424..d45b9e8a 100644 --- a/src/constants.js +++ b/src/constants.js @@ -6,4 +6,9 @@ module.exports.defaultAddonBlueprintName = 'addon'; module.exports.glimmerPackageName = '@glimmer/blueprint'; +module.exports.defaultAppPackageName = + '@ember-tooling/classic-build-app-blueprint'; +module.exports.defaultAddonPackageName = + '@ember-tooling/classic-build-addon-blueprint'; + module.exports.defaultTo = '*'; diff --git a/src/get-base-blueprint.js b/src/get-base-blueprint.js index d2951fd0..e5d7658b 100644 --- a/src/get-base-blueprint.js +++ b/src/get-base-blueprint.js @@ -17,25 +17,29 @@ const isDefaultBlueprint = require('./is-default-blueprint'); */ async function getBaseBlueprint({ cwd, blueprints, blueprint }) { let baseBlueprint; - let isCustomBlueprint = !isDefaultBlueprint(blueprint); if (isCustomBlueprint && !blueprint.isBaseBlueprint) { baseBlueprint = blueprints.find(b => b.isBaseBlueprint); + if (baseBlueprint) { baseBlueprint = loadSafeBlueprint(baseBlueprint); + let isCustomBlueprint = !isDefaultBlueprint(baseBlueprint); + if (isCustomBlueprint) { if (baseBlueprint.location) { let parsedPackage = await parseBlueprintPackage({ cwd, packageName: baseBlueprint.location }); + let downloadedPackage = await downloadPackage( baseBlueprint.packageName, parsedPackage.url, baseBlueprint.version ); + baseBlueprint.path = downloadedPackage.path; } } diff --git a/src/get-project-options.js b/src/get-project-options.js index 6b694f43..09e821ca 100644 --- a/src/get-project-options.js +++ b/src/get-project-options.js @@ -57,11 +57,8 @@ module.exports = async function getProjectOptions( } let projectType = getProjectType(checkForDep, keywords); - let options = [projectType]; - let cwd = process.cwd(); - let isYarn = await hasYarn(cwd); if (isYarn) { diff --git a/src/get-start-and-end-commands.js b/src/get-start-and-end-commands.js index 8075a198..b7e0e1ce 100644 --- a/src/get-start-and-end-commands.js +++ b/src/get-start-and-end-commands.js @@ -11,7 +11,11 @@ const overwriteBlueprintFiles = require('./overwrite-blueprint-files'); const debug = require('./debug'); const npm = require('boilerplate-update/src/npm'); const mutatePackageJson = require('boilerplate-update/src/mutate-package-json'); -const { glimmerPackageName } = require('./constants'); +const { + glimmerPackageName, + defaultAddonPackageName, + defaultAppPackageName +} = require('./constants'); const hasYarn = require('./has-yarn'); const nodeModulesIgnore = ` @@ -19,6 +23,34 @@ const nodeModulesIgnore = ` /node_modules/ `; +/** + * @typedef {Object} StartAndEndCommandsResult + * @property {string} projectName + * @property {string} packageName - Always 'ember-cli' + * @property {string} commandName - Always 'ember' + * @property {Function} createProjectFromCache - Function to create project from cache + * @property {Function} createProjectFromRemote - Function to create project from remote + * @property {Object} startOptions - Start configuration options + * @property {Object} startOptions.baseBlueprint - Base blueprint for start + * @property {Object} startOptions.blueprint - Blueprint for start + * @property {string} startOptions.packageRange - Package range for start + * @property {Object} endOptions - End configuration options + * @property {Object} endOptions.baseBlueprint - Base blueprint for end + * @property {Object} endOptions.blueprint - Blueprint for end + * @property {string} endOptions.packageRange - Package range for end + */ + +/** + * Creates start and end commands configuration for ember-cli-update process + * + * @param {Object} params - Configuration parameters + * @param {Object} params.packageJson - The package.json object of the project + * @param {Object} [params.baseBlueprint] - Base blueprint configuration object + * @param {Object} [params.startBlueprint] - Starting blueprint configuration object + * @param {Object} params.endBlueprint - Target blueprint configuration object + * @param {Object} [params.emberCliUpdateJson] - ember-cli-update.json configuration object + * @returns {StartAndEndCommandsResult} Configuration object with project details and command options + */ module.exports = function getStartAndEndCommands({ packageJson, baseBlueprint, @@ -95,13 +127,23 @@ async function isDefaultAddonBlueprint(blueprint) { let isCustomBlueprint = !isDefaultBlueprint(blueprint); let isDefaultAddonBlueprint; + if (blueprint.packageName === defaultAppPackageName) { + return false; + } + + if (blueprint.packageName === defaultAddonPackageName) { + return true; + } if (isCustomBlueprint) { let keywords; + if (blueprint.path) { keywords = utils.require(path.join(blueprint.path, 'package')).keywords; } else { - keywords = await npm.json('v', blueprint.packageName, 'keywords'); + let packageInfo = await npm.json('v', blueprint.packageName); + + keywords = packageInfo.keywords; } isDefaultAddonBlueprint = !( diff --git a/src/index.js b/src/index.js index bf3741f3..f99ea2fc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ 'use strict'; +const semver = require('semver'); const getProjectOptions = require('./get-project-options'); const getPackageName = require('./get-package-name'); const getVersions = require('boilerplate-update/src/get-versions'); @@ -19,15 +20,33 @@ const findBlueprint = require('./find-blueprint'); const getBaseBlueprint = require('./get-base-blueprint'); const chooseBlueprintUpdates = require('./choose-blueprint-updates'); const getBlueprintFilePath = require('./get-blueprint-file-path'); +const saveBlueprintFile = require('./save-blueprint-file'); const resolvePackage = require('./resolve-package'); -const { defaultTo } = require('./constants'); +const { + defaultTo, + defaultPackageName, + defaultAppBlueprintName, + defaultAppPackageName +} = require('./constants'); const normalizeBlueprintArgs = require('./normalize-blueprint-args'); +const EMBER_CLI_BLUEPRINT_VITE_BOUNDARY = '6.8.0-beta.1'; + +/** + * @typedef {Object} Blueprint + * @property {string} name + * @property {string[]} options - args passed to the blueprint + * @property {string} packageName - The name of the package containing the blueprint + * @property {string} location + * @property {string} version + * @property {boolean} isBaseBlueprint + */ + /** * If `version` attribute exists in the `blueprint` object and URL is empty, skip. Otherwise resolve the details of * the blueprint * - * @param {Object} blueprint - Expected to contain `name`, `options` array, `packageName`, `location`, and `version` + * @param {Blueprint} blueprint - Expected to contain `name`, `options` array, `packageName`, `location`, and `version` * attributes * @param {String} url - Optional parameter that links to package * @param {String} range - Version range i.e. 1.0.2 @@ -64,11 +83,11 @@ module.exports = async function emberCliUpdate({ // so we can no longer look it up on the fly after the run. // We must rely on a lookup before the run. let emberCliUpdateJsonPath = await getBlueprintFilePath(cwd); - let emberCliUpdateJson = await loadSafeBlueprintFile(emberCliUpdateJsonPath); let { blueprints } = emberCliUpdateJson; + /** @type {Blueprint} */ let blueprint; let packageUrl; @@ -77,14 +96,14 @@ module.exports = async function emberCliUpdate({ packageName, blueprintName: _blueprint }); - let parsedPackage = await parseBlueprintPackage({ cwd, packageName: blueprintArgs.packageName }); - packageUrl = parsedPackage.url; + packageUrl = parsedPackage.url; packageName = parsedPackage.name; + if (!packageName) { let downloadedPackage = await downloadPackage( null, @@ -93,11 +112,11 @@ module.exports = async function emberCliUpdate({ ); packageName = downloadedPackage.name; } - let blueprintName; + + let blueprintName = packageName; + if (blueprintArgs.blueprintName !== blueprintArgs.packageName) { blueprintName = blueprintArgs.blueprintName; - } else { - blueprintName = packageName; } let existingBlueprint = findBlueprint( @@ -105,9 +124,10 @@ module.exports = async function emberCliUpdate({ packageName, blueprintName ); - if (existingBlueprint) { - blueprint = existingBlueprint; - } else { + + blueprint = existingBlueprint; + + if (!existingBlueprint) { blueprint = loadSafeBlueprint({ packageName, name: blueprintName, @@ -164,8 +184,6 @@ module.exports = async function emberCliUpdate({ packageUrl = parsedPackage.url; } - let isCustomBlueprint = !isDefaultBlueprint(blueprint); - let baseBlueprint = await getBaseBlueprint({ cwd, blueprints, @@ -190,27 +208,46 @@ module.exports = async function emberCliUpdate({ ); } - let endBlueprint; + /** @type {Blueprint} */ + let startBlueprint = { ...blueprint }; + /** @type {Blueprint} */ + let endBlueprint = { ...blueprint }; + delete endBlueprint.version; let { promise, resolveConflictsProcess } = await boilerplateUpdate({ cwd, projectOptions: ({ packageJson }) => getProjectOptions(packageJson, blueprint), mergeOptions: async function mergeOptions({ packageJson, projectOptions }) { - let startBlueprint = { ...blueprint }; - endBlueprint = { ...blueprint }; - delete endBlueprint.version; + if (isDefaultBlueprint(blueprint)) { + let packageName = getPackageName(projectOptions); + let versions = await getVersions(packageName); + let getTagVersion = _getTagVersion(versions, packageName); - if (isCustomBlueprint) { + endBlueprint.version = await getTagVersion(to); + + if ( + endBlueprint.packageName === defaultPackageName && + endBlueprint.name === defaultAppBlueprintName && + semver.gte(endBlueprint.version, EMBER_CLI_BLUEPRINT_VITE_BOUNDARY) + ) { + let { url } = await parseBlueprintPackage({ + cwd, + packageName: defaultAppPackageName + }); + + endBlueprint.packageName = defaultAppPackageName; + endBlueprint.name = defaultAppPackageName; + delete endBlueprint.codemodsSource; + delete endBlueprint.outputRepo; + + await _resolvePackage(endBlueprint, url, to); + } + } else { await Promise.all([ _resolvePackage(startBlueprint, packageUrl, startBlueprint.version), _resolvePackage(endBlueprint, packageUrl, to) ]); - } else { - let packageName = getPackageName(projectOptions); - let versions = await getVersions(packageName); - let getTagVersion = _getTagVersion(versions, packageName); - endBlueprint.version = await getTagVersion(to); } let customDiffOptions = getStartAndEndCommands({ @@ -236,6 +273,20 @@ module.exports = async function emberCliUpdate({ promise: (async () => { let result = await promise; + if ( + endBlueprint.packageName === defaultAppPackageName && + semver.gte(endBlueprint.version, EMBER_CLI_BLUEPRINT_VITE_BOUNDARY) + ) { + // Drop legacy ember-cli app blueprint from list of blueprints + emberCliUpdateJson.blueprints = emberCliUpdateJson.blueprints.filter( + ({ name, packageName }) => + name !== defaultAppBlueprintName && + packageName !== defaultPackageName + ); + + await saveBlueprintFile(emberCliUpdateJsonPath, emberCliUpdateJson); + } + await saveBlueprint({ emberCliUpdateJsonPath, blueprint: endBlueprint diff --git a/src/is-default-blueprint.js b/src/is-default-blueprint.js index 3744bd6c..578a9db7 100644 --- a/src/is-default-blueprint.js +++ b/src/is-default-blueprint.js @@ -18,6 +18,7 @@ function isDefaultBlueprint({ packageName, name }) { if (packageName === glimmerPackageName && name === glimmerPackageName) { return true; } + return ( packageName === defaultPackageName && [defaultAppBlueprintName, defaultAddonBlueprintName].includes(name) diff --git a/src/load-default-blueprint.js b/src/load-default-blueprint.js index 4f4ded6b..51c602ce 100644 --- a/src/load-default-blueprint.js +++ b/src/load-default-blueprint.js @@ -13,6 +13,7 @@ function loadDefaultBlueprint(projectOptions = [], version) { let packageName = defaultPackageName; let name; let codemodsSource; + if (projectOptions.includes('addon')) { name = defaultAddonBlueprintName; codemodsSource = 'ember-addon-codemods-manifest@1'; @@ -25,6 +26,7 @@ function loadDefaultBlueprint(projectOptions = [], version) { } let options = []; + if (!projectOptions.includes('glimmer')) { if (projectOptions.includes('yarn')) { options.push('--yarn');