UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

332 lines (331 loc) • 13.1 kB
"use strict"; 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); }; }