nx
Version:
332 lines (331 loc) • 13.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.recordInitialMigrationMetadata = recordInitialMigrationMetadata;
exports.finishMigrationProcess = finishMigrationProcess;
exports.runSingleMigration = runSingleMigration;
exports.getImplementationPath = getImplementationPath;
exports.modifyMigrationsJsonMetadata = modifyMigrationsJsonMetadata;
exports.addSuccessfulMigration = addSuccessfulMigration;
exports.updateRefForSuccessfulMigration = updateRefForSuccessfulMigration;
exports.addFailedMigration = addFailedMigration;
exports.addSkippedMigration = addSkippedMigration;
exports.addStoppedMigration = addStoppedMigration;
exports.readMigrationsJsonMetadata = readMigrationsJsonMetadata;
exports.undoMigration = undoMigration;
exports.killMigrationProcess = killMigrationProcess;
exports.stopMigration = stopMigration;
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const path_1 = require("path");
const migrate_1 = require("./migrate");
let currentMigrationProcess = null;
let currentMigrationId = null;
let migrationCancelled = false;
function recordInitialMigrationMetadata(workspacePath, versionToMigrateTo) {
const migrationsJsonPath = (0, path_1.join)(workspacePath, 'migrations.json');
const parsedMigrationsJson = JSON.parse((0, fs_1.readFileSync)(migrationsJsonPath, 'utf-8'));
const gitRef = (0, child_process_1.execSync)('git rev-parse HEAD', {
cwd: workspacePath,
encoding: 'utf-8',
}).trim();
const gitSubject = (0, child_process_1.execSync)('git log -1 --pretty=%s', {
cwd: workspacePath,
encoding: 'utf-8',
}).trim();
parsedMigrationsJson['nx-console'] = {
initialGitRef: {
ref: gitRef,
subject: gitSubject,
},
targetVersion: versionToMigrateTo,
};
(0, fs_1.writeFileSync)(migrationsJsonPath, JSON.stringify(parsedMigrationsJson, null, 2));
}
function finishMigrationProcess(workspacePath, squashCommits, commitMessage) {
const migrationsJsonPath = (0, path_1.join)(workspacePath, 'migrations.json');
const parsedMigrationsJson = JSON.parse((0, fs_1.readFileSync)(migrationsJsonPath, 'utf-8'));
const initialGitRef = parsedMigrationsJson['nx-console'].initialGitRef;
if ((0, fs_1.existsSync)(migrationsJsonPath)) {
(0, fs_1.rmSync)(migrationsJsonPath);
}
(0, child_process_1.execSync)('git add .', {
cwd: workspacePath,
encoding: 'utf-8',
});
(0, child_process_1.execSync)(`git commit -m "${commitMessage}" --no-verify`, {
cwd: workspacePath,
encoding: 'utf-8',
});
if (squashCommits && initialGitRef) {
(0, child_process_1.execSync)(`git reset --soft ${initialGitRef.ref}`, {
cwd: workspacePath,
encoding: 'utf-8',
});
(0, child_process_1.execSync)(`git commit -m "${commitMessage}" --no-verify`, {
cwd: workspacePath,
encoding: 'utf-8',
});
}
}
async function runSingleMigration(workspacePath, migration, configuration) {
try {
// Set current migration tracking
currentMigrationId = migration.id;
migrationCancelled = false;
modifyMigrationsJsonMetadata(workspacePath, addRunningMigration(migration.id));
const gitRefBefore = (0, child_process_1.execSync)('git rev-parse HEAD', {
cwd: workspacePath,
encoding: 'utf-8',
}).trim();
// Run migration in a separate process so it can be cancelled
const runMigrationProcessPath = require.resolve('./run-migration-process.js');
const migrationProcess = (0, child_process_1.spawn)('node', [
runMigrationProcessPath,
workspacePath,
migration.id,
migration.package,
migration.name,
migration.version,
configuration.createCommits.toString(),
configuration.commitPrefix || 'chore: [nx migration] ',
], {
cwd: workspacePath,
stdio: ['pipe', 'pipe', 'pipe'],
});
// Track the process for cancellation
currentMigrationProcess = migrationProcess;
// Handle process output
let output = '';
migrationProcess.stdout.on('data', (data) => {
output += data.toString();
});
migrationProcess.stderr.on('data', (data) => {
console.error('Migration stderr:', data.toString());
});
// Wait for the process to complete
const exitCode = await new Promise((resolve, reject) => {
migrationProcess.on('close', (code) => {
resolve(code);
});
migrationProcess.on('error', (error) => {
reject(error);
});
});
currentMigrationProcess = null;
if (exitCode !== 0) {
throw new Error(`Migration process exited with code ${exitCode}`);
}
// Parse the result from the migration process (extract the JSON output)
const jsonStr = output
.trim()
.split('\n')
.find((line) => line.startsWith('{'));
const result = JSON.parse(jsonStr);
if (result.type === 'error') {
throw new Error(result.message);
}
const { fileChanges, gitRefAfter, nextSteps } = result;
modifyMigrationsJsonMetadata(workspacePath, addSuccessfulMigration(migration.id, fileChanges.map((change) => ({
path: change.path,
type: change.type,
})), gitRefAfter, nextSteps));
if (gitRefBefore !== gitRefAfter) {
(0, child_process_1.execSync)('git add migrations.json', {
cwd: workspacePath,
encoding: 'utf-8',
});
(0, child_process_1.execSync)('git commit --amend --no-verify --no-edit', {
cwd: workspacePath,
encoding: 'utf-8',
});
// The revision changes after the amend, so we need to update it
const amendedGitRef = (0, child_process_1.execSync)('git rev-parse HEAD', {
cwd: workspacePath,
encoding: 'utf-8',
}).trim();
modifyMigrationsJsonMetadata(workspacePath, updateRefForSuccessfulMigration(migration.id, amendedGitRef));
}
}
catch (e) {
// Check if migration was cancelled/stopped
if (migrationCancelled && currentMigrationId === migration.id) {
// Migration was stopped by user, don't add as failed since it's already marked as stopped
console.log(`Migration ${migration.id} was stopped by user`);
}
else {
// Migration failed normally
modifyMigrationsJsonMetadata(workspacePath, addFailedMigration(migration.id, e.message));
}
}
finally {
// Clear the tracking variables
currentMigrationProcess = null;
currentMigrationId = null;
migrationCancelled = false;
modifyMigrationsJsonMetadata(workspacePath, removeRunningMigration(migration.id));
(0, child_process_1.execSync)('git add migrations.json', {
cwd: workspacePath,
encoding: 'utf-8',
});
}
}
async function getImplementationPath(workspacePath, migration) {
const { collection, collectionPath } = (0, migrate_1.readMigrationCollection)(migration.package, workspacePath);
const { path } = (0, migrate_1.getImplementationPath)(collection, collectionPath, migration.name);
return path;
}
function modifyMigrationsJsonMetadata(workspacePath, modify) {
const migrationsJsonPath = (0, path_1.join)(workspacePath, 'migrations.json');
const migrationsJson = JSON.parse((0, fs_1.readFileSync)(migrationsJsonPath, 'utf-8'));
migrationsJson['nx-console'] = modify(migrationsJson['nx-console']);
(0, fs_1.writeFileSync)(migrationsJsonPath, JSON.stringify(migrationsJson, null, 2));
}
function addSuccessfulMigration(id, fileChanges, ref, nextSteps) {
return (migrationsJsonMetadata) => {
const copied = { ...migrationsJsonMetadata };
if (!copied.completedMigrations) {
copied.completedMigrations = {};
}
copied.completedMigrations = {
...copied.completedMigrations,
[id]: {
type: 'successful',
name: id,
changedFiles: fileChanges,
ref,
nextSteps,
},
};
return copied;
};
}
function updateRefForSuccessfulMigration(id, ref) {
return (migrationsJsonMetadata) => {
const copied = { ...migrationsJsonMetadata };
if (!copied.completedMigrations) {
copied.completedMigrations = {};
}
const existing = copied.completedMigrations[id];
if (existing && existing.type === 'successful') {
existing.ref = ref;
}
else {
throw new Error(`Attempted to update ref for unsuccessful migration`);
}
return copied;
};
}
function addFailedMigration(id, error) {
return (migrationsJsonMetadata) => {
const copied = { ...migrationsJsonMetadata };
if (!copied.completedMigrations) {
copied.completedMigrations = {};
}
copied.completedMigrations = {
...copied.completedMigrations,
[id]: {
type: 'failed',
name: id,
error,
},
};
return copied;
};
}
function addSkippedMigration(id) {
return (migrationsJsonMetadata) => {
const copied = { ...migrationsJsonMetadata };
if (!copied.completedMigrations) {
copied.completedMigrations = {};
}
copied.completedMigrations = {
...copied.completedMigrations,
[id]: {
type: 'skipped',
},
};
return copied;
};
}
function addStoppedMigration(id, error) {
return (migrationsJsonMetadata) => {
const copied = { ...migrationsJsonMetadata };
if (!copied.completedMigrations) {
copied.completedMigrations = {};
}
copied.completedMigrations = {
...copied.completedMigrations,
[id]: {
type: 'stopped',
name: id,
error,
},
};
return copied;
};
}
function addRunningMigration(id) {
return (migrationsJsonMetadata) => {
migrationsJsonMetadata.runningMigrations = [
...(migrationsJsonMetadata.runningMigrations ?? []),
id,
];
return migrationsJsonMetadata;
};
}
function removeRunningMigration(id) {
return (migrationsJsonMetadata) => {
migrationsJsonMetadata.runningMigrations =
migrationsJsonMetadata.runningMigrations?.filter((n) => n !== id);
return migrationsJsonMetadata;
};
}
function readMigrationsJsonMetadata(workspacePath) {
const migrationsJsonPath = (0, path_1.join)(workspacePath, 'migrations.json');
const migrationsJson = JSON.parse((0, fs_1.readFileSync)(migrationsJsonPath, 'utf-8'));
return migrationsJson['nx-console'];
}
function undoMigration(workspacePath, id) {
return (migrationsJsonMetadata) => {
const existing = migrationsJsonMetadata.completedMigrations[id];
if (existing.type !== 'successful')
throw new Error(`undoMigration called on unsuccessful migration: ${id}`);
(0, child_process_1.execSync)(`git reset --hard ${existing.ref}^`, {
cwd: workspacePath,
encoding: 'utf-8',
});
migrationsJsonMetadata.completedMigrations[id] = {
type: 'skipped',
};
return migrationsJsonMetadata;
};
}
function killMigrationProcess(migrationId, workspacePath) {
try {
if (workspacePath) {
modifyMigrationsJsonMetadata(workspacePath, stopMigration(migrationId));
}
// Check if this is the currently running migration and kill the process
if (currentMigrationId === migrationId && currentMigrationProcess) {
currentMigrationProcess.kill('SIGTERM');
// Some processes may not respond to SIGTERM immediately,
// so we give it a short timeout before forcefully killing it
setTimeout(() => {
if (currentMigrationProcess && !currentMigrationProcess.killed) {
currentMigrationProcess.kill('SIGKILL');
}
}, 2000);
}
return true;
}
catch (error) {
console.error(`Failed to stop migration ${migrationId}:`, error);
return false;
}
}
function stopMigration(migrationId) {
return (migrationsJsonMetadata) => {
const updated = addStoppedMigration(migrationId, 'Migration was stopped by user')(migrationsJsonMetadata);
return removeRunningMigration(migrationId)(updated);
};
}