@yolkai/nx-tao
Version:
432 lines (431 loc) • 18.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@angular-devkit/core");
const core = require("@angular-devkit/core/node");
const node_1 = require("@angular-devkit/core/node");
const workflow_1 = require("@angular-devkit/schematics/src/workflow");
const tools_1 = require("@angular-devkit/schematics/tools");
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const path = require("path");
const semver_1 = require("semver");
const stripJsonComments = require("strip-json-comments");
const tmp_1 = require("tmp");
const logger_1 = require("../shared/logger");
const params_1 = require("../shared/params");
const minimist = require("minimist");
class Migrator {
constructor(opts) {
this.versions = opts.versions;
this.fetch = opts.fetch;
this.from = opts.from;
this.to = opts.to;
}
updatePackageJson(targetPackage, targetVersion) {
return __awaiter(this, void 0, void 0, function* () {
const packageJson = yield this._updatePackageJson(targetPackage, { version: targetVersion, alwaysAddToPackageJson: false }, {});
const migrations = yield this._createMigrateJson(packageJson);
return { packageJson, migrations };
});
}
_createMigrateJson(versions) {
return __awaiter(this, void 0, void 0, function* () {
const migrations = yield Promise.all(Object.keys(versions).map((c) => __awaiter(this, void 0, void 0, function* () {
const currentVersion = this.versions(c);
if (currentVersion === null)
return [];
const target = versions[c];
const migrationsJson = yield this.fetch(c, target.version);
if (!migrationsJson.schematics)
return [];
return Object.keys(migrationsJson.schematics)
.filter(r => this.gt(migrationsJson.schematics[r].version, currentVersion) &
this.lte(migrationsJson.schematics[r].version, target.version))
.map(r => (Object.assign({}, migrationsJson.schematics[r], { package: c, name: r })));
})));
return migrations.reduce((m, c) => [...m, ...c], []);
});
}
_updatePackageJson(targetPackage, target, collectedVersions) {
return __awaiter(this, void 0, void 0, function* () {
let targetVersion = target.version;
if (this.to[targetPackage]) {
targetVersion = this.to[targetPackage];
}
if (!this.versions(targetPackage)) {
return {
[targetPackage]: {
version: target.version,
alwaysAddToPackageJson: !!target.alwaysAddToPackageJson
}
};
}
let migrationsJson;
try {
migrationsJson = yield this.fetch(targetPackage, targetVersion);
targetVersion = migrationsJson.version;
}
catch (e) {
if (e.message.indexOf('No matching version') > -1) {
throw new Error(`${e.message}\nRun migrate with --to="package1@version1,package2@version2"`);
}
else {
throw e;
}
}
const packages = this.collapsePackages(targetPackage, targetVersion, migrationsJson);
const childCalls = yield Promise.all(Object.keys(packages)
.filter(r => {
return (!collectedVersions[r] ||
this.gt(packages[r].version, collectedVersions[r].version));
})
.map(u => this._updatePackageJson(u, packages[u], Object.assign({}, collectedVersions, { [targetPackage]: target }))));
return childCalls.reduce((m, c) => {
Object.keys(c).forEach(r => {
if (!m[r] || this.gt(c[r].version, m[r].version)) {
m[r] = c[r];
}
});
return m;
}, {
[targetPackage]: {
version: migrationsJson.version,
alwaysAddToPackageJson: target.alwaysAddToPackageJson || false
}
});
});
}
collapsePackages(packageName, targetVersion, m) {
// this should be used to know what version to include
// we should use from everywhere we use versions
if (packageName === '@yolkai/nx-workspace') {
if (!m.packageJsonUpdates)
m.packageJsonUpdates = {};
m.packageJsonUpdates[targetVersion + '-defaultPackages'] = {
version: targetVersion,
packages: [
'@yolkai/nx-angular',
'@yolkai/nx-cypress',
'@yolkai/eslint-plugin-nx',
'@yolkai/nx-express',
'@yolkai/nx-jest',
'@yolkai/nx-linter',
'@yolkai/nx-nest',
'@yolkai/nx-next',
'@yolkai/nx-node',
'@yolkai/nx-react',
'@yolkai/nx-storybook',
'@yolkai/nx-tao',
'@yolkai/nx-web'
].reduce((m, c) => (Object.assign({}, m, { [c]: { version: targetVersion, alwaysAddToPackageJson: false } })), {})
};
}
if (!m.packageJsonUpdates || !this.versions(packageName))
return {};
return Object.keys(m.packageJsonUpdates)
.filter(r => {
return (this.gt(m.packageJsonUpdates[r].version, this.versions(packageName)) && this.lte(m.packageJsonUpdates[r].version, targetVersion));
})
.map(r => m.packageJsonUpdates[r].packages)
.map(packages => {
if (!packages)
return {};
return Object.keys(packages)
.filter(p => !packages[p].ifPackageInstalled ||
this.versions(packages[p].ifPackageInstalled))
.reduce((m, c) => (Object.assign({}, m, { [c]: {
version: packages[c].version,
alwaysAddToPackageJson: packages[c].alwaysAddToPackageJson
} })), {});
})
.reduce((m, c) => (Object.assign({}, m, c)), {});
}
gt(v1, v2) {
return semver_1.gt(this.normalizeVersion(v1), this.normalizeVersion(v2));
}
lte(v1, v2) {
return semver_1.lte(this.normalizeVersion(v1), this.normalizeVersion(v2));
}
normalizeVersion(v) {
if (v.startsWith('8-'))
return '8.0.0-beta.1';
if (v.startsWith('9-'))
return '9.0.0-beta.1';
if (v.startsWith('10-'))
return '9.0.0-beta.1';
if (v.startsWith('11-'))
return '9.0.0-beta.1';
return v;
}
}
exports.Migrator = Migrator;
function parseMigrationsOptions(args) {
const options = params_1.convertToCamelCase(minimist(args, {
string: ['runMigrations', 'from', 'to'],
alias: {
runMigrations: 'run-migrations'
}
}));
if (!options.runMigrations) {
let from = {};
if (options.from) {
options.from.split(',').forEach(p => {
const split = p.lastIndexOf('@');
from[p.substring(0, split)] = p.substring(split + 1);
});
}
let to = {};
if (options.to) {
options.to.split(',').forEach(p => {
const split = p.lastIndexOf('@');
to[p.substring(0, split)] = p.substring(split + 1);
});
}
let targetPackage;
let targetVersion;
if (args[0] && args[0].indexOf('@') > 1) {
const i = args[0].lastIndexOf('@');
targetPackage = args[0].substring(0, i);
targetVersion = args[0].substring(i + 1);
}
else if (args[0]) {
targetPackage = '@yolkai/nx-workspace';
targetVersion = args[0];
}
else {
targetPackage = '@yolkai/nx-workspace';
targetVersion = 'latest';
}
return {
type: 'generateMigrations',
targetPackage,
targetVersion,
from,
to
};
}
else {
return { type: 'runMigrations', runMigrations: options.runMigrations };
}
}
function versions(root, from) {
return (packageName) => {
try {
if (from[packageName]) {
return from[packageName];
}
const content = fs_1.readFileSync(path.join(root, `./node_modules/${packageName}/package.json`));
return JSON.parse(stripJsonComments(content.toString()))['version'];
}
catch (e) {
return null;
}
};
}
// testing-fetch-start
function createFetcher(logger) {
let cache = {};
return function f(packageName, packageVersion) {
return __awaiter(this, void 0, void 0, function* () {
if (!cache[`${packageName}-${packageVersion}`]) {
const dir = tmp_1.dirSync().name;
logger.info(`Fetching ${packageName}@${packageVersion}`);
child_process_1.execSync(`npm install ${packageName}@${packageVersion} --prefix=${dir}`, {
stdio: []
});
const json = JSON.parse(stripJsonComments(fs_1.readFileSync(path.join(dir, 'node_modules', packageName, 'package.json')).toString()));
let migrationsFile = json['nx-migrations'] || json['ng-update'];
// migrationsFile is an object
if (migrationsFile && migrationsFile.migrations) {
migrationsFile = migrationsFile.migrations;
}
// packageVersion can be a tag, resolvedVersion works with semver
const resolvedVersion = json.version;
try {
if (migrationsFile && typeof migrationsFile === 'string') {
const json = JSON.parse(stripJsonComments(fs_1.readFileSync(path.join(dir, 'node_modules', packageName, migrationsFile)).toString()));
cache[`${packageName}-${packageVersion}`] = {
version: resolvedVersion,
schematics: json.schematics,
packageJsonUpdates: json.packageJsonUpdates
};
}
else {
cache[`${packageName}-${packageVersion}`] = {
version: resolvedVersion
};
}
}
catch (e) {
logger.warn(`Could not find '${migrationsFile}' in '${packageName}'. Skipping it`);
cache[`${packageName}-${packageVersion}`] = {
version: resolvedVersion
};
}
}
return cache[`${packageName}-${packageVersion}`];
});
};
}
// testing-fetch-end
function createMigrationsFile(root, migrations) {
fs_1.writeFileSync(path.join(root, 'migrations.json'), JSON.stringify({ migrations }, null, 2));
}
function updatePackageJson(root, updatedPackages) {
const packageJsonPath = path.join(root, 'package.json');
const json = JSON.parse(stripJsonComments(fs_1.readFileSync(packageJsonPath).toString()));
Object.keys(updatedPackages).forEach(p => {
if (json.devDependencies && json.devDependencies[p]) {
json.devDependencies[p] = updatedPackages[p].version;
}
else if (json.dependencies && json.dependencies[p]) {
json.dependencies[p] = updatedPackages[p].version;
}
else if (updatedPackages[p].alwaysAddToPackageJson) {
if (!json.dependencies)
json.dependencies = {};
json.dependencies[p] = updatedPackages[p].version;
}
});
fs_1.writeFileSync(packageJsonPath, JSON.stringify(json, null, 2));
}
function generateMigrationsJsonAndUpdatePackageJson(logger, root, opts) {
return __awaiter(this, void 0, void 0, function* () {
try {
logger.info(`Fetching meta data about packages.`);
logger.info(`It may take a few minutes.`);
const migrator = new Migrator({
versions: versions(root, opts.from),
fetch: createFetcher(logger),
from: opts.from,
to: opts.to
});
const { migrations, packageJson } = yield migrator.updatePackageJson(opts.targetPackage, opts.targetVersion);
updatePackageJson(root, packageJson);
if (migrations.length > 0) {
createMigrationsFile(root, migrations);
logger.info(`The migrate command has run successfully.`);
logger.info(`- package.json has been updated`);
logger.info(`- migrations.json has been generated`);
logger.info(`Next steps:`);
logger.info(`- Make sure package.json changes make sense and then run 'npm install' or 'yarn'`);
logger.info(`- Run 'nx migrate --run-migrations=migrations.json'`);
}
else {
logger.info(`The migrate command has run successfully.`);
logger.info(`- package.json has been updated`);
logger.info(`- there are no migrations to run, so migrations.json has not been created.`);
}
}
catch (e) {
const startVersion = versions(root, {})('@yolkai/nx-workspace');
logger.error(`The migrate command failed. Try the following to migrate your workspace:`);
logger.error(`> npm install --save-dev @yolkai/nx-workspace@latest`);
logger.error(`> nx migrate ${opts.targetPackage}@${opts.targetVersion} --from="@yolkai/nx-workspace@${startVersion}"`);
logger.error(`This will use the newest version of the migrate functionality, which might have your issue resolved.`);
logger.error(`----------------------------------------------------------------------------------------------------`);
throw e;
}
});
}
class MigrationEngineHost extends tools_1.NodeModulesEngineHost {
constructor() {
super();
}
_resolveCollectionPath(name) {
let collectionPath = undefined;
if (name.replace(/\\/g, '/').split('/').length > (name[0] == '@' ? 2 : 1)) {
try {
collectionPath = this._resolvePath(name, process.cwd());
}
catch (_a) { }
}
if (!collectionPath) {
let packageJsonPath = this._resolvePackageJson(name, process.cwd());
if (!core.fs.isFile(packageJsonPath)) {
packageJsonPath = path.join(packageJsonPath, 'package.json');
}
let pkgJsonSchematics = require(packageJsonPath)['nx-migrations'];
if (!pkgJsonSchematics) {
pkgJsonSchematics = require(packageJsonPath)['ng-update'];
if (!pkgJsonSchematics) {
throw new Error(`Could find migrations in package: "${name}"`);
}
}
if (typeof pkgJsonSchematics != 'string') {
pkgJsonSchematics = pkgJsonSchematics.migrations;
}
collectionPath = this._resolvePath(pkgJsonSchematics, path.dirname(packageJsonPath));
}
try {
if (collectionPath) {
JSON.parse(stripJsonComments(fs_1.readFileSync(collectionPath).toString()));
return collectionPath;
}
}
catch (e) {
throw new Error(`Invalid migration file in package: "${name}"`);
}
throw new Error(`Collection cannot be resolved: "${name}"`);
}
}
class MigrationsWorkflow extends workflow_1.BaseWorkflow {
constructor(host) {
super({
host,
engineHost: new MigrationEngineHost(),
force: true,
dryRun: false
});
}
}
function runMigrations(logger, root, opts) {
return __awaiter(this, void 0, void 0, function* () {
const migrationsFile = JSON.parse(stripJsonComments(fs_1.readFileSync(path.join(root, opts.runMigrations)).toString()));
const host = new core_1.virtualFs.ScopedHost(new node_1.NodeJsSyncHost(), core_1.normalize(root));
const workflow = new MigrationsWorkflow(host);
let p = Promise.resolve(null);
migrationsFile.migrations.forEach(m => {
p = p.then(() => {
logger.info(`Running migration ${m.package}:${m.name}`);
return workflow
.execute({
collection: m.package,
schematic: m.name,
options: {},
debug: false,
logger
})
.toPromise()
.then(() => {
logger.info(`Successfully finished ${m.package}:${m.name}`);
logger.info(`---------------------------------------------------------`);
});
});
});
yield p;
});
}
function migrate(root, args, isVerbose = false) {
return __awaiter(this, void 0, void 0, function* () {
const logger = logger_1.getLogger(isVerbose);
return params_1.handleErrors(logger, isVerbose, () => __awaiter(this, void 0, void 0, function* () {
const opts = parseMigrationsOptions(args);
if (opts.type === 'generateMigrations') {
yield generateMigrationsJsonAndUpdatePackageJson(logger, root, opts);
}
else {
yield runMigrations(logger, root, opts);
}
}));
});
}
exports.migrate = migrate;