// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
import assert = require("assert");
import * as path from "path";
import * as fs from "fs";
import { ErrorHelper } from "../../common/error/errorHelper";
import { InternalErrorCode } from "../../common/error/internalErrorCode";
import { ProjectVersionHelper } from "../../common/projectVersionHelper";
import { SettingsHelper } from "../settingsHelper";
import { ChildProcess } from "../../common/node/childProcess";
import { OutputChannelLogger } from "../log/OutputChannelLogger";
import { HostPlatform, HostPlatformId } from "../../common/hostPlatform";
import { ReactNativeCommand } from "./util/reactNativeCommand";
const logger = OutputChannelLogger.getMainChannel();
export class CleanRestartPackager extends ReactNativeCommand {
codeName = "cleanRestartPackager";
label = "Clean & Restart Packager";
error = ErrorHelper.getInternalError(InternalErrorCode.FailedToCleanRestartPackager);
async baseFn(): Promise<void> {
assert(this.project);
const nodeModulesRoot = this.project.getOrUpdateNodeModulesRoot();
await ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(nodeModulesRoot);
const projectPath = this.project.getPackager().getProjectPath();
const packagerPort = SettingsHelper.getPackagerPort(
this.project.getWorkspaceFolderUri().fsPath,
);
logger.info("Starting Metro Packager cleanup and restart...");
// Step 1: Kill Metro process on port 8081
await this.killMetroProcess(packagerPort);
// Step 2: Clean Metro cache
await this.cleanMetroCache(projectPath);
// Step 3: Clean Watchman cache (if available)
await this.cleanWatchmanCache();
// Step 4: Restart packager with reset cache
logger.info("Restarting Packager with clean cache...");
await this.project.getPackager().restart(packagerPort);
logger.info("Metro Packager cleanup and restart completed successfully.");
}
private async killMetroProcess(port: number): Promise<void> {
logger.info(`Step 1/3: Terminating Metro process on port ${port}...`);
try {
const platformId = HostPlatform.getPlatformId();
const childProcess = new ChildProcess();
if (platformId === HostPlatformId.WINDOWS) {
// Windows: Use netstat and taskkill
try {
const netstatResult = await childProcess.exec(
`netstat -ano | findstr :${port}`,
);
const outcome = await netstatResult.outcome;
if (outcome) {
// Extract PID from netstat output
const lines = outcome.split("\n");
for (const line of lines) {
const match = line.match(/\s+LISTENING\s+(\d+)/);
if (match && match[1]) {
const pid = match[1];
logger.info(`Found Metro process with PID: ${pid}`);
await childProcess.exec(`taskkill /PID ${pid} /F /T`);
logger.info(`Successfully terminated process ${pid}`);
}
}
}
} catch (error) {
logger.info(`No Metro process found on port ${port}`);
}
} else {
// macOS/Linux: Use lsof and kill
try {
const lsofResult = await childProcess.exec(`lsof -ti:${port}`);
const outcome = await lsofResult.outcome;
if (outcome && outcome.trim()) {
const pid = outcome.trim();
logger.info(`Found Metro process with PID: ${pid}`);
await childProcess.exec(`kill -9 ${pid}`);
logger.info(`Successfully terminated process ${pid}`);
}
} catch (error) {
logger.info(`No Metro process found on port ${port}`);
}
}
} catch (error) {
logger.warning(`Failed to kill Metro process: ${error}`);
}
}
private async cleanMetroCache(projectPath: string): Promise<void> {
logger.info("Step 2/3: Cleaning Metro cache...");
const metroCachePath = path.join(projectPath, "node_modules", ".cache", "metro");
try {
if (fs.existsSync(metroCachePath)) {
// Use recursive directory deletion
await this.deleteDirectory(metroCachePath);
logger.info(`Successfully cleaned Metro cache at: ${metroCachePath}`);
} else {
logger.info("Metro cache directory not found, skipping...");
}
} catch (error) {
logger.warning(`Failed to clean Metro cache: ${error}`);
}
}
private async cleanWatchmanCache(): Promise<void> {
logger.info("Step 3/3: Cleaning Watchman cache...");
try {
const childProcess = new ChildProcess();
const watchmanResult = await childProcess.exec("watchman watch-del-all");
await watchmanResult.outcome;
logger.info("Successfully cleaned Watchman cache");
} catch (error) {
logger.info("Watchman not available or failed to clean cache, continuing...");
}
}
private async deleteDirectory(dirPath: string): Promise<void> {
if (fs.existsSync(dirPath)) {
const files = fs.readdirSync(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
await this.deleteDirectory(filePath);
} else {
fs.unlinkSync(filePath);
}
}
fs.rmdirSync(dirPath);
}
}
}microsoft/vscode-react-native
Publicmirrored from https://github.com/microsoft/vscode-react-nativeAvailable
src/extension/commands/cleanRestartPackager.ts
152lines · modepreview