ember-cli-update
Version:
Update Ember CLI projects
329 lines (272 loc) • 8.94 kB
JavaScript
;
const path = require('path');
const fs = require('fs-extra');
const execa = require('execa');
const { spawn } = require('./run');
const utils = require('./utils');
const isDefaultBlueprint = require('./is-default-blueprint');
const installAndGenerateBlueprint = require('./install-and-generate-blueprint');
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 hasYarn = require('./has-yarn');
const nodeModulesIgnore = `
/node_modules/
`;
// remove when node 8 is dropped
const lastNode8Version = '3.16';
module.exports = function getStartAndEndCommands({
packageJson,
baseBlueprint,
startBlueprint,
endBlueprint,
emberCliUpdateJson
}) {
// ember-cli-update.json can override projectName and we should use the name field in package.json if it doesn't exist
let projectName = emberCliUpdateJson?.projectName ?? packageJson.name;
// if we haven't overridden projectName in ember-cli-update or we don't have a name in the package.json file
// default to containing dir name
if (!projectName) {
let pathParts = process.cwd().split(path.sep);
projectName = pathParts[pathParts.length - 1];
}
if (baseBlueprint && !baseBlueprint.isBaseBlueprint) {
throw new Error('The intended base blueprint is not actually a base blueprint.');
}
if (endBlueprint.isBaseBlueprint && baseBlueprint) {
throw new Error('You supplied two layers of base blueprints.');
}
let isCustomBlueprint = !isDefaultBlueprint(endBlueprint);
let isGlimmer = endBlueprint.packageName === glimmerPackageName && endBlueprint.name === glimmerPackageName;
let startRange;
let endRange;
if (!isCustomBlueprint && !isGlimmer) {
startRange = startBlueprint && startBlueprint.version;
endRange = endBlueprint.version;
} else if (!endBlueprint.isBaseBlueprint && isDefaultBlueprint(baseBlueprint)) {
startRange = endRange = baseBlueprint.version;
} else {
// first version that supports blueprints with versions
// `-b foo@1.2.3`
// https://github.com/ember-cli/ember-cli/pull/8571
startRange = endRange = `>=3.11.0-beta.1 <${lastNode8Version}`;
}
return {
projectName,
packageName: 'ember-cli',
commandName: 'ember',
createProjectFromCache: createProject(runEmberLocally),
createProjectFromRemote: createProject(runEmberRemotely),
startOptions: {
baseBlueprint,
blueprint: startBlueprint,
// for cache detection logic
packageRange: startRange
},
endOptions: {
baseBlueprint,
blueprint: endBlueprint,
// for cache detection logic
packageRange: endRange
}
};
};
async function isDefaultAddonBlueprint(blueprint) {
let isCustomBlueprint = !isDefaultBlueprint(blueprint);
let isDefaultAddonBlueprint;
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');
}
isDefaultAddonBlueprint = !(keywords && keywords.includes('ember-blueprint'));
}
return isDefaultAddonBlueprint;
}
function getArgs({
projectName,
directoryName,
blueprint
}) {
let args = [
'new',
projectName
];
if (directoryName !== projectName) {
args.push(`-dir=${directoryName}`);
}
args.push('--skip-git');
let _blueprint;
if (blueprint.path) {
// Only use path when necessary, because `npm install <folder>`
// symlinks instead of actually installing, so any peerDeps won't
// work. Example https://github.com/salsify/ember-cli-dependency-lint/blob/v1.0.3/lib/commands/dependency-lint.js#L5
_blueprint = blueprint.path;
} else {
let isCustomBlueprint = !isDefaultBlueprint(blueprint);
let isGlimmer = blueprint.packageName === glimmerPackageName && blueprint.name === glimmerPackageName;
if (isCustomBlueprint || isGlimmer) {
_blueprint = `${blueprint.packageName}@${blueprint.version}`;
} else {
_blueprint = blueprint.name;
}
}
return [
...args,
'--skip-npm',
'-b',
_blueprint,
...blueprint.options
];
}
module.exports.spawn = function spawn(command, args, options) {
debug(`${command} ${args.join(' ')}`);
let ps = execa(command, args, {
stdio: ['pipe', 'pipe', 'inherit'],
...options
});
ps.stdout.pipe(process.stdout);
return ps;
};
module.exports.npx = function npx(args, options) {
return utils.npx(args.join(' '), options);
};
function runEmberLocally({
packageRoot,
cwd,
args
}) {
return module.exports.spawn('node', [
path.join(packageRoot, 'bin/ember'),
...args
], { cwd });
}
function runEmberRemotely({
cwd,
blueprint,
args
}) {
let isCustomBlueprint = !isDefaultBlueprint(blueprint);
let isGlimmer = blueprint.packageName === glimmerPackageName && blueprint.name === glimmerPackageName;
if (isCustomBlueprint || isGlimmer) {
args = ['ember-cli@latest', ...args];
// args = ['-p', 'github:ember-cli/ember-cli#cfb9780', 'ember', 'new', projectName, `-dir=${directoryName}, '-sg', -sn', '-b', `${blueprint.packageName}@${blueprint.version}`];
} else {
args = ['-p', `ember-cli@${blueprint.version}`, 'ember', ...args];
}
return module.exports.npx(args, { cwd });
}
function createProject(runEmber) {
return ({
packageRoot,
options: {
projectName,
baseBlueprint,
blueprint
}
}) => {
return async function createProject(cwd) {
if (!blueprint) {
return cwd;
}
let projectRoot;
async function _runEmber(blueprint) {
async function __runEmber() {
// remove scope
let directoryName = projectName.replace(/^@.+\//, '');
let args = getArgs({
projectName,
directoryName,
blueprint
});
await runEmber({
packageRoot,
cwd,
blueprint,
args
});
projectRoot = path.join(cwd, directoryName);
}
try {
await __runEmber();
} catch (err) {
// We currently do not support a name of `...`.
projectName = 'my-project';
await __runEmber();
}
}
if (await isDefaultAddonBlueprint(blueprint)) {
let isYarnProject = await hasYarn(projectRoot);
await _runEmber(baseBlueprint);
await module.exports.installAddonBlueprint({
projectRoot,
blueprint,
packageManager: isYarnProject ? 'yarn' : 'npm'
});
} else {
await _runEmber(blueprint);
}
if (!isDefaultBlueprint(blueprint)) {
// This might not be needed anymore.
await module.exports.appendNodeModulesIgnore({
projectRoot
});
}
return projectRoot;
};
};
}
/**
* Install packages from package.json then install the specified package and generate blueprint
*
* @param {string} projectRoot - Used as cwd for running npm commands as well directory to make file modifications
* @param {object} blueprint - Expected to have `packageName`, `version`, and `path` (nullable) attributes
* @param {string} packageManager - Expected to be either `npm` or `yarn`
* @returns {Promise<void>}
*/
module.exports.installAddonBlueprint = async function installAddonBlueprint({
projectRoot,
blueprint,
packageManager
}) {
// `not found: ember` without this
await spawn(packageManager, ['install'], { cwd: projectRoot });
let { ps } = await installAndGenerateBlueprint({
cwd: projectRoot,
packageName: blueprint.packageName,
version: blueprint.version,
blueprintPath: blueprint.path,
blueprintName: blueprint.name,
blueprintOptions: blueprint.options,
stdin: 'pipe',
packageManager
});
overwriteBlueprintFiles(ps);
await ps;
await mutatePackageJson(projectRoot, packageJson => {
packageJson.devDependencies[blueprint.packageName] = blueprint.version;
});
await fs.remove(path.join(projectRoot, 'package-lock.json'));
};
async function appendNodeModulesIgnore({
projectRoot
}) {
if (!await fs.pathExists(path.join(projectRoot, 'node_modules'))) {
return;
}
let isIgnoringNodeModules;
let gitignore = '';
try {
gitignore = await fs.readFile(path.join(projectRoot, '.gitignore'), 'utf8');
isIgnoringNodeModules = /^\/?node_modules\/?$/m.test(gitignore);
} catch (err) {}
if (!isIgnoringNodeModules) {
await fs.writeFile(path.join(projectRoot, '.gitignore'), `${gitignore}${nodeModulesIgnore}`);
}
}
module.exports.appendNodeModulesIgnore = appendNodeModulesIgnore;
module.exports.getArgs = getArgs;