diff --git a/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/scripts/codegen/__tests__/generate-artifacts-executor-test.js index b53806d26f778f..17a78e9d542186 100644 --- a/scripts/codegen/__tests__/generate-artifacts-executor-test.js +++ b/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -201,3 +201,228 @@ describe('extractLibrariesFromJSON', () => { }); }); }); + +describe('delete empty files and folders', () => { + beforeEach(() => { + jest.resetModules(); + }); + + it('when path is empty file, deletes it', () => { + const targetFilepath = 'my-file.txt'; + let statSyncInvocationCount = 0; + let rmSyncInvocationCount = 0; + let rmdirSyncInvocationCount = 0; + jest.mock('fs', () => ({ + statSync: filepath => { + statSyncInvocationCount += 1; + expect(filepath).toBe(targetFilepath); + return { + isFile: () => { + return true; + }, + size: 0, + }; + }, + rmSync: filepath => { + rmSyncInvocationCount += 1; + expect(filepath).toBe(targetFilepath); + }, + rmdirSync: filepath => { + rmdirSyncInvocationCount += 1; + }, + })); + + underTest._cleanupEmptyFilesAndFolders(targetFilepath); + expect(statSyncInvocationCount).toBe(1); + expect(rmSyncInvocationCount).toBe(1); + expect(rmdirSyncInvocationCount).toBe(0); + }); + + it('when path is not an empty file, does nothing', () => { + const targetFilepath = 'my-file.txt'; + const size = 128; + + let statSyncInvocationCount = 0; + let rmSyncInvocationCount = 0; + let rmdirSyncInvocationCount = 0; + + jest.mock('fs', () => ({ + statSync: filepath => { + statSyncInvocationCount += 1; + expect(filepath).toBe(targetFilepath); + return { + isFile: () => { + return true; + }, + size: size, + }; + }, + rmSync: filepath => { + rmSyncInvocationCount += 1; + }, + rmdirSync: filepath => { + rmdirSyncInvocationCount += 1; + }, + })); + + underTest._cleanupEmptyFilesAndFolders(targetFilepath); + expect(statSyncInvocationCount).toBe(1); + expect(rmSyncInvocationCount).toBe(0); + expect(rmdirSyncInvocationCount).toBe(0); + }); + + it("when path is folder and it's empty, removes it", () => { + const targetFolder = 'build/'; + const content = []; + + let statSyncInvocationCount = 0; + let readdirInvocationCount = 0; + let rmSyncInvocationCount = 0; + let rmdirSyncInvocationCount = 0; + + jest.mock('fs', () => ({ + statSync: filepath => { + statSyncInvocationCount += 1; + expect(filepath).toBe(targetFolder); + return { + isFile: () => { + return false; + }, + }; + }, + rmSync: filepath => { + rmSyncInvocationCount += 1; + }, + rmdirSync: filepath => { + rmdirSyncInvocationCount += 1; + expect(filepath).toBe(targetFolder); + }, + readdirSync: filepath => { + readdirInvocationCount += 1; + return content; + }, + })); + + underTest._cleanupEmptyFilesAndFolders(targetFolder); + expect(statSyncInvocationCount).toBe(1); + expect(readdirInvocationCount).toBe(2); + expect(rmSyncInvocationCount).toBe(0); + expect(rmdirSyncInvocationCount).toBe(1); + }); + + it("when path is folder and it's not empty, removes only empty folders and files", () => { + const targetFolder = 'build/'; + const content = ['emptyFolder', 'emptyFile', 'notEmptyFile']; + + const files = ['build/emptyFile', 'build/notEmptyFile']; + + const emptyContent = []; + const fileSizes = { + 'build/emptyFile': 0, + 'build/notEmptyFile': 32, + }; + + let statSyncInvocation = []; + let rmSyncInvocation = []; + let rmdirSyncInvocation = []; + let readdirInvocation = []; + + jest.mock('fs', () => ({ + statSync: filepath => { + statSyncInvocation.push(filepath); + + return { + isFile: () => { + return files.includes(filepath); + }, + size: fileSizes[filepath], + }; + }, + rmSync: filepath => { + rmSyncInvocation.push(filepath); + }, + rmdirSync: filepath => { + rmdirSyncInvocation.push(filepath); + }, + readdirSync: filepath => { + readdirInvocation.push(filepath); + return filepath === targetFolder ? content : emptyContent; + }, + })); + + underTest._cleanupEmptyFilesAndFolders(targetFolder); + expect(statSyncInvocation).toEqual([ + 'build/', + 'build/emptyFolder', + 'build/emptyFile', + 'build/notEmptyFile', + ]); + expect(readdirInvocation).toEqual([ + 'build/', + 'build/emptyFolder', + 'build/emptyFolder', + 'build/', + ]); + expect(rmSyncInvocation).toEqual(['build/emptyFile']); + expect(rmdirSyncInvocation).toEqual(['build/emptyFolder']); + }); + + it('when path is folder and it contains only empty folders, removes everything', () => { + const targetFolder = 'build/'; + const content = ['emptyFolder1', 'emptyFolder2']; + const emptyContent = []; + + let statSyncInvocation = []; + let rmSyncInvocation = []; + let rmdirSyncInvocation = []; + let readdirInvocation = []; + + jest.mock('fs', () => ({ + statSync: filepath => { + statSyncInvocation.push(filepath); + + return { + isFile: () => { + return false; + }, + }; + }, + rmSync: filepath => { + rmSyncInvocation.push(filepath); + }, + rmdirSync: filepath => { + rmdirSyncInvocation.push(filepath); + }, + readdirSync: filepath => { + readdirInvocation.push(filepath); + return filepath === targetFolder + ? content.filter( + element => + !rmdirSyncInvocation.includes(path.join(targetFolder, element)), + ) + : emptyContent; + }, + })); + + underTest._cleanupEmptyFilesAndFolders(targetFolder); + expect(statSyncInvocation).toEqual([ + 'build/', + 'build/emptyFolder1', + 'build/emptyFolder2', + ]); + expect(readdirInvocation).toEqual([ + 'build/', + 'build/emptyFolder1', + 'build/emptyFolder1', + 'build/emptyFolder2', + 'build/emptyFolder2', + 'build/', + ]); + expect(rmSyncInvocation).toEqual([]); + expect(rmdirSyncInvocation).toEqual([ + 'build/emptyFolder1', + 'build/emptyFolder2', + 'build/', + ]); + }); +}); diff --git a/scripts/codegen/generate-artifacts-executor.js b/scripts/codegen/generate-artifacts-executor.js index 784f44a2631afb..abae278a91110b 100644 --- a/scripts/codegen/generate-artifacts-executor.js +++ b/scripts/codegen/generate-artifacts-executor.js @@ -355,6 +355,39 @@ function createComponentProvider( } } +// It removes all the empty files and empty folders +// it finds, starting from `filepath`, recursively. +// +// This function is needed since, after aligning the codegen between +// iOS and Android, we have to create empty folders in advance and +// we don't know wheter they will be populated up until the end of the process. +// +// @parameter filepath: the root path from which we want to remove the empty files and folders. +function cleanupEmptyFilesAndFolders(filepath) { + const stats = fs.statSync(filepath); + + if (stats.isFile() && stats.size === 0) { + fs.rmSync(filepath); + return; + } else if (stats.isFile()) { + return; + } + + const dirContent = fs.readdirSync(filepath); + dirContent.forEach(contentPath => + cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)), + ); + + // The original folder may be filled with empty folders + // if that the case, we would also like to remove the parent. + // Hence, we need to read the folder again. + const newContent = fs.readdirSync(filepath); + if (newContent.length === 0) { + fs.rmdirSync(filepath); + return; + } +} + // Execute /** @@ -428,6 +461,7 @@ function execute( ); createComponentProvider(fabricEnabled, schemaPaths, node, iosOutputDir); + cleanupEmptyFilesAndFolders(iosOutputDir); } catch (err) { console.error(err); process.exitCode = 1; @@ -443,4 +477,5 @@ module.exports = { _extractLibrariesFromJSON: extractLibrariesFromJSON, _executeNodeScript: executeNodeScript, _generateCode: generateCode, + _cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders, };