From 623bdee7624be5a59fc64cc28c5d4a88c5331930 Mon Sep 17 00:00:00 2001 From: Harshith Rai Date: Thu, 14 May 2026 13:40:42 +0530 Subject: [PATCH 1/4] fix: remove stale connection files from directory export when connections are deleted --- src/context/directory/handlers/connections.ts | 22 +++++++ test/context/directory/connections.test.js | 65 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/context/directory/handlers/connections.ts b/src/context/directory/handlers/connections.ts index f8075f979..70875ba11 100644 --- a/src/context/directory/handlers/connections.ts +++ b/src/context/directory/handlers/connections.ts @@ -78,6 +78,28 @@ async function dump(context: DirectoryContext): Promise { const connectionsFolder = path.join(context.filePath, constants.CONNECTIONS_DIRECTORY); fs.ensureDirSync(connectionsFolder); + // Build the set of expected filenames for the current connections so stale + // files left over from previously-exported connections can be removed. + const expectedFiles = new Set( + connections.flatMap((connection) => { + const connectionName = sanitize(connection.name); + const files = [`${connectionName}.json`]; + if (connection.strategy === 'email') { + files.push(`${connectionName}.html`); + } + return files; + }) + ); + + // Remove stale files that no longer correspond to a current connection. + getFiles(connectionsFolder, ['.json', '.html']).forEach((filePath) => { + const fileName = path.basename(filePath); + if (!expectedFiles.has(fileName)) { + log.info(`Deleting stale connection file: ${filePath}`); + fs.removeSync(filePath); + } + }); + // Convert enabled_clients from id to name connections.forEach((connection) => { let dumpedConnection = { diff --git a/test/context/directory/connections.test.js b/test/context/directory/connections.test.js index e8e7df179..c2ccaf2d0 100644 --- a/test/context/directory/connections.test.js +++ b/test/context/directory/connections.test.js @@ -243,4 +243,69 @@ describe('#directory context connections', () => { expect(fs.existsSync(path.join(connectionsFolder, 'includedConnection.json'))).to.equal(true); expect(fs.existsSync(path.join(connectionsFolder, 'excludedConnection.json'))).to.equal(false); }); + + it('should create empty connections directory and parse it as empty array', async () => { + const dir = path.join(testDataDir, 'directory', 'connectionsEmptyDump'); + cleanThenMkdir(dir); + const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); + context.assets.connections = []; + + await handler.dump(context); + + const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY); + expect(fs.existsSync(connectionsFolder)).to.equal(true); + expect(fs.readdirSync(connectionsFolder).filter((f) => f.endsWith('.json'))).to.deep.equal([]); + + // Verify parse of empty directory returns [] not null + const parseContext = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); + await parseContext.loadAssetsFromLocal(); + expect(parseContext.assets.connections).to.deep.equal([]); + }); + + it('should remove stale connection files when connections are removed from source', async () => { + const dir = path.join(testDataDir, 'directory', 'connectionsStaleFiles'); + cleanThenMkdir(dir); + + // Simulate a previous export that created connection files + const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY); + fs.ensureDirSync(connectionsFolder); + fs.writeFileSync(path.join(connectionsFolder, 'facebook.json'), '{"name":"facebook"}'); + fs.writeFileSync(path.join(connectionsFolder, 'github.json'), '{"name":"github"}'); + + const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); + // Re-export with only one connection (facebook removed) + context.assets.connections = [{ name: 'github', strategy: 'github' }]; + + await handler.dump(context); + + expect(fs.existsSync(path.join(connectionsFolder, 'github.json'))).to.equal(true); + expect(fs.existsSync(path.join(connectionsFolder, 'facebook.json'))).to.equal(false); + }); + + it('should remove all stale connection files when all connections are removed from source', async () => { + const dir = path.join(testDataDir, 'directory', 'connectionsAllStale'); + cleanThenMkdir(dir); + + // Simulate a previous export that created connection files + const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY); + fs.ensureDirSync(connectionsFolder); + fs.writeFileSync(path.join(connectionsFolder, 'facebook.json'), '{"name":"facebook"}'); + fs.writeFileSync(path.join(connectionsFolder, 'github.json'), '{"name":"github"}'); + + const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); + // Re-export with zero connections + context.assets.connections = []; + + await handler.dump(context); + + const remainingJsonFiles = fs + .readdirSync(connectionsFolder) + .filter((f) => f.endsWith('.json')); + expect(remainingJsonFiles).to.deep.equal([]); + + // Verify parse of now-empty directory returns [] not null (enabling deletions on import) + const parseContext = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); + await parseContext.loadAssetsFromLocal(); + expect(parseContext.assets.connections).to.deep.equal([]); + }); }); From b72016d76797ca5ff053fa0d1a5b69e7028e526e Mon Sep 17 00:00:00 2001 From: Harshith Rai Date: Mon, 18 May 2026 18:09:06 +0530 Subject: [PATCH 2/4] test: remove redundant tests --- test/context/directory/connections.test.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/test/context/directory/connections.test.js b/test/context/directory/connections.test.js index c2ccaf2d0..44adf4bdf 100644 --- a/test/context/directory/connections.test.js +++ b/test/context/directory/connections.test.js @@ -244,24 +244,6 @@ describe('#directory context connections', () => { expect(fs.existsSync(path.join(connectionsFolder, 'excludedConnection.json'))).to.equal(false); }); - it('should create empty connections directory and parse it as empty array', async () => { - const dir = path.join(testDataDir, 'directory', 'connectionsEmptyDump'); - cleanThenMkdir(dir); - const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); - context.assets.connections = []; - - await handler.dump(context); - - const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY); - expect(fs.existsSync(connectionsFolder)).to.equal(true); - expect(fs.readdirSync(connectionsFolder).filter((f) => f.endsWith('.json'))).to.deep.equal([]); - - // Verify parse of empty directory returns [] not null - const parseContext = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); - await parseContext.loadAssetsFromLocal(); - expect(parseContext.assets.connections).to.deep.equal([]); - }); - it('should remove stale connection files when connections are removed from source', async () => { const dir = path.join(testDataDir, 'directory', 'connectionsStaleFiles'); cleanThenMkdir(dir); From 0766e8a59d00802f0849de3c331e71626a007492 Mon Sep 17 00:00:00 2001 From: Harshith Rai Date: Mon, 18 May 2026 18:21:25 +0530 Subject: [PATCH 3/4] style: run prettier on connections test file --- test/context/directory/connections.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/context/directory/connections.test.js b/test/context/directory/connections.test.js index 44adf4bdf..c470232bf 100644 --- a/test/context/directory/connections.test.js +++ b/test/context/directory/connections.test.js @@ -280,9 +280,7 @@ describe('#directory context connections', () => { await handler.dump(context); - const remainingJsonFiles = fs - .readdirSync(connectionsFolder) - .filter((f) => f.endsWith('.json')); + const remainingJsonFiles = fs.readdirSync(connectionsFolder).filter((f) => f.endsWith('.json')); expect(remainingJsonFiles).to.deep.equal([]); // Verify parse of now-empty directory returns [] not null (enabling deletions on import) From 79aece8adbcd72cbabaf02c0aab4b245acb5c5a3 Mon Sep 17 00:00:00 2001 From: Harshith Rai Date: Tue, 19 May 2026 13:25:59 +0530 Subject: [PATCH 4/4] fix: remove stale connection files from directory export when connections are deleted --- src/context/directory/handlers/connections.ts | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/context/directory/handlers/connections.ts b/src/context/directory/handlers/connections.ts index 70875ba11..61b558157 100644 --- a/src/context/directory/handlers/connections.ts +++ b/src/context/directory/handlers/connections.ts @@ -76,29 +76,7 @@ async function dump(context: DirectoryContext): Promise { } const connectionsFolder = path.join(context.filePath, constants.CONNECTIONS_DIRECTORY); - fs.ensureDirSync(connectionsFolder); - - // Build the set of expected filenames for the current connections so stale - // files left over from previously-exported connections can be removed. - const expectedFiles = new Set( - connections.flatMap((connection) => { - const connectionName = sanitize(connection.name); - const files = [`${connectionName}.json`]; - if (connection.strategy === 'email') { - files.push(`${connectionName}.html`); - } - return files; - }) - ); - - // Remove stale files that no longer correspond to a current connection. - getFiles(connectionsFolder, ['.json', '.html']).forEach((filePath) => { - const fileName = path.basename(filePath); - if (!expectedFiles.has(fileName)) { - log.info(`Deleting stale connection file: ${filePath}`); - fs.removeSync(filePath); - } - }); + fs.emptyDirSync(connectionsFolder); // Convert enabled_clients from id to name connections.forEach((connection) => {