diff --git a/src/dev/create-extension.ts b/src/dev/create-extension.ts index 7eecea9dd..3c33336a9 100644 --- a/src/dev/create-extension.ts +++ b/src/dev/create-extension.ts @@ -37,13 +37,32 @@ const getProjectPath = (rawArgs: string[]) => { return { projectPath }; }; -const getChangedFilesFromFirstCommit = async (projectPath: string): Promise => { +const getDeletedFiles = async (projectPath: string): Promise => { + const { stdout: allFiles } = await execa( + "git", + ["log", "--all", "--diff-filter=ACDMRT", "--name-only", "--format="], + { cwd: projectPath }, + ); + + const { stdout: currentFiles } = await execa("git", ["ls-files"], { + cwd: projectPath, + }); + + const allFilesSet = new Set(allFiles.split("\n").filter(Boolean)); + const currentFilesArray = currentFiles.split("\n").filter(Boolean); + + return Array.from(allFilesSet).filter(file => !currentFilesArray.includes(file)); +}; + +const getChangedFilesSinceFirstCommit = async (projectPath: string): Promise => { const { stdout: firstCommit } = await execa("git", ["rev-list", "--max-parents=0", "HEAD"], { cwd: projectPath, }); - const { stdout } = await execa("git", ["diff", "--name-only", `${firstCommit.trim()}..HEAD`], { + + const { stdout } = await execa("git", ["diff", "--diff-filter=d", "--name-only", `${firstCommit.trim()}..HEAD`], { cwd: projectPath, }); + return stdout.split("\n").filter(Boolean); }; @@ -77,13 +96,47 @@ const findTemplateFiles = async (dir: string, templates: Set) => { } }; -const copyFiles = async (files: string[], projectName: string, projectPath: string, templates: Set) => { - for (const file of files) { +const copyChanges = async ( + changedFiles: string[], + deletedFiles: string[], + projectName: string, + projectPath: string, + templates: Set, +) => { + for (const file of deletedFiles) { + const destPath = path.join(EXTERNAL_EXTENSIONS_DIR, projectName, TARGET_EXTENSION_DIR, file); + if (fs.existsSync(destPath)) { + await fs.promises.unlink(destPath); + prettyLog.success(`Removed deleted file: ${file}`, 2); + console.log("\n"); + + // remove empty directories + const dirPath = path.dirname(destPath); + try { + const remainingFiles = await fs.promises.readdir(dirPath); + if (remainingFiles.length === 0) { + await fs.promises.rmdir(dirPath); + prettyLog.success(`Removed empty directory: ${path.relative(EXTERNAL_EXTENSIONS_DIR, dirPath)}`, 2); + console.log("\n"); + } + } catch { + // directory might already be deleted, ignore error + } + } + } + + for (const file of changedFiles) { const pathSegmentsOfFile = file.split(path.sep); const sourcePath = path.resolve(projectPath, file); const destPath = path.join(EXTERNAL_EXTENSIONS_DIR, projectName, TARGET_EXTENSION_DIR, file); const sourceFileName = path.basename(sourcePath); + if (!fs.existsSync(sourcePath)) { + prettyLog.warning(`Source file not found, skipping: ${file}`, 2); + console.log("\n"); + continue; + } + if (templates.has(file)) { prettyLog.warning(`Skipping file: ${file}`, 2); prettyLog.info(`Please instead create/update: ${destPath}.args.mjs`, 3); @@ -150,15 +203,16 @@ const main = async (rawArgs: string[]) => { prettyLog.info(`Extension name: ${projectName}\n`); prettyLog.info("Getting list of changed files...", 1); - const changedFiles = await getChangedFilesFromFirstCommit(projectPath); + const changedFiles = await getChangedFilesSinceFirstCommit(projectPath); + const deletedFiles = await getDeletedFiles(projectPath); - if (changedFiles.length === 0) { - prettyLog.warning("No changed files to copy.", 1); + if (changedFiles.length === 0 && deletedFiles.length === 0) { + prettyLog.warning("There are no file changes to copy.", 1); console.log("\n"); } else { prettyLog.info(`Found ${changedFiles.length} changed files, processing them...`, 1); console.log("\n"); - await copyFiles(changedFiles, projectName, projectPath, templates); + await copyChanges(changedFiles, deletedFiles, projectName, projectPath, templates); } prettyLog.info(`Files processed successfully, updated ${EXTERNAL_EXTENSIONS_DIR}/${projectName} directory.`);