From 437ac22e0a89af2fb46ccc3d703d141ad2d8dfc4 Mon Sep 17 00:00:00 2001 From: misha-db Date: Fri, 27 Mar 2026 13:16:15 +0400 Subject: [PATCH 1/3] Detect include files from parent folders --- .../src/bundle/BundleFileSet.test.ts | 68 +++++++++++++++++++ .../src/bundle/BundleFileSet.ts | 65 +++++++++++------- 2 files changed, 108 insertions(+), 25 deletions(-) diff --git a/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts b/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts index 05bf2a275..9245457ee 100644 --- a/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts +++ b/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts @@ -73,6 +73,74 @@ describe(__filename, async function () { expect(await bundleFileSet.getRootFile()).to.be.undefined; }); + describe("parent-directory includes", async () => { + it("getIncludedFiles should find files referenced via .. paths", async () => { + // Structure: tmpdir/shared/config.yml (included), tmpdir/project/sub/ (project root) + const sharedDir = path.join(tmpdir.path, "shared"); + const projectDir = path.join(tmpdir.path, "project", "sub"); + await fs.mkdir(sharedDir, {recursive: true}); + await fs.mkdir(projectDir, {recursive: true}); + + const sharedFile = path.join(sharedDir, "config.yml"); + await fs.writeFile(sharedFile, ""); + + const rootBundleData: BundleSchema = { + include: ["../../shared/config.yml"], + }; + await fs.writeFile( + path.join(projectDir, "databricks.yml"), + yaml.stringify(rootBundleData) + ); + + const mockWFM = mock(); + const mockWF = mock(); + const projectUri = Uri.file(projectDir); + when(mockWF.uri).thenReturn(projectUri); + when(mockWFM.activeWorkspaceFolder).thenReturn(instance(mockWF)); + when(mockWFM.activeProjectUri).thenReturn(projectUri); + const bundleFileSet = new BundleFileSet(instance(mockWFM)); + + const files = await bundleFileSet.getIncludedFiles(); + expect(files?.map((f) => f.fsPath)).to.deep.equal([sharedFile]); + }); + + it("isIncludedBundleFile should return true for files referenced via .. paths", async () => { + const sharedDir = path.join(tmpdir.path, "shared"); + const projectDir = path.join(tmpdir.path, "project", "sub"); + await fs.mkdir(sharedDir, {recursive: true}); + await fs.mkdir(projectDir, {recursive: true}); + + const sharedFile = path.join(sharedDir, "config.yml"); + await fs.writeFile(sharedFile, ""); + + const rootBundleData: BundleSchema = { + include: ["../../shared/config.yml", "local.yml"], + }; + await fs.writeFile( + path.join(projectDir, "databricks.yml"), + yaml.stringify(rootBundleData) + ); + + const mockWFM = mock(); + const mockWF = mock(); + const projectUri = Uri.file(projectDir); + when(mockWF.uri).thenReturn(projectUri); + when(mockWFM.activeWorkspaceFolder).thenReturn(instance(mockWF)); + when(mockWFM.activeProjectUri).thenReturn(projectUri); + const bundleFileSet = new BundleFileSet(instance(mockWFM)); + + expect( + await bundleFileSet.isIncludedBundleFile(Uri.file(sharedFile)) + ).to.be.true; + + expect( + await bundleFileSet.isIncludedBundleFile( + Uri.file(path.join(projectDir, "other.yml")) + ) + ).to.be.false; + }); + }); + describe("file listing", async () => { beforeEach(async () => { const rootBundleData: BundleSchema = { diff --git a/packages/databricks-vscode/src/bundle/BundleFileSet.ts b/packages/databricks-vscode/src/bundle/BundleFileSet.ts index f362e004e..641a65765 100644 --- a/packages/databricks-vscode/src/bundle/BundleFileSet.ts +++ b/packages/databricks-vscode/src/bundle/BundleFileSet.ts @@ -88,33 +88,47 @@ export class BundleFileSet { return Uri.file(rootFile[0]); } - async getIncludedFilesGlob() { + private async getIncludePatterns(): Promise { const rootFile = await this.getRootFile(); if (rootFile === undefined) { - return undefined; + return []; + } + const bundle = await parseBundleYaml(rootFile); + if (!bundle?.include?.length) { + return []; } - const bundle = await parseBundleYaml(Uri.file(rootFile.fsPath)); - if (bundle?.include === undefined || bundle?.include.length === 0) { + return bundle.include; + } + + async getIncludedFilesGlob() { + const patterns = await this.getIncludePatterns(); + if (patterns.length === 0) { return undefined; } - if (bundle?.include.length === 1) { - return bundle.include[0]; + if (patterns.length === 1) { + return patterns[0]; } - return `{${bundle.include.join(",")}}`; + return `{${patterns.join(",")}}`; } async getIncludedFiles() { - const includedFilesGlob = await this.getIncludedFilesGlob(); - if (includedFilesGlob !== undefined) { - return ( - await glob.glob( - toGlobPath( - path.join(this.projectRoot.fsPath, includedFilesGlob) - ), - {nocase: process.platform === "win32"} - ) - ).map((i) => Uri.file(i)); + const patterns = await this.getIncludePatterns(); + if (patterns.length === 0) { + return undefined; + } + + const allFiles: string[] = []; + for (const pattern of patterns) { + const absolutePattern = toGlobPath( + path.resolve(this.projectRoot.fsPath, pattern) + ); + const files = await glob.glob(absolutePattern, { + nocase: process.platform === "win32", + }); + allFiles.push(...files); } + + return [...new Set(allFiles)].map((f) => Uri.file(f)); } async allFiles() { @@ -152,15 +166,16 @@ export class BundleFileSet { } async isIncludedBundleFile(e: Uri) { - let includedFilesGlob = await this.getIncludedFilesGlob(); - if (includedFilesGlob === undefined) { - return false; + const patterns = await this.getIncludePatterns(); + for (const pattern of patterns) { + const absolutePattern = toGlobPath( + path.resolve(this.projectRoot.fsPath, pattern) + ); + if (minimatch(toGlobPath(e.fsPath), absolutePattern)) { + return true; + } } - includedFilesGlob = getAbsoluteGlobPath( - includedFilesGlob, - this.projectRoot - ); - return minimatch(e.fsPath, toGlobPath(includedFilesGlob)); + return false; } async isBundleFile(e: Uri) { From 4c60ca378dbecb3bf655bf92a890fe8185f404c4 Mon Sep 17 00:00:00 2001 From: misha-db Date: Sat, 28 Mar 2026 01:24:40 +0400 Subject: [PATCH 2/3] Update BundleFileSet.test.ts --- .../src/bundle/BundleFileSet.test.ts | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts b/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts index 9245457ee..7b4c4347b 100644 --- a/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts +++ b/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts @@ -8,6 +8,7 @@ import {BundleSchema} from "./types"; import * as yaml from "yaml"; import {instance, mock, when} from "ts-mockito"; import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager"; +import {minimatch} from "minimatch"; describe(__filename, async function () { let tmpdir: tmp.DirectoryResult; @@ -20,10 +21,10 @@ describe(__filename, async function () { await tmpdir.cleanup(); }); - function getWorkspaceFolderManagerMock() { + function getWorkspaceFolderManagerMock(projectDir?: string) { const mockWorkspaceFolderManager = mock(); const mockWorkspaceFolder = mock(); - const uri = Uri.file(tmpdir.path); + const uri = Uri.file(projectDir ?? tmpdir.path); when(mockWorkspaceFolder.uri).thenReturn(uri); when(mockWorkspaceFolderManager.activeWorkspaceFolder).thenReturn( instance(mockWorkspaceFolder) @@ -92,13 +93,9 @@ describe(__filename, async function () { yaml.stringify(rootBundleData) ); - const mockWFM = mock(); - const mockWF = mock(); - const projectUri = Uri.file(projectDir); - when(mockWF.uri).thenReturn(projectUri); - when(mockWFM.activeWorkspaceFolder).thenReturn(instance(mockWF)); - when(mockWFM.activeProjectUri).thenReturn(projectUri); - const bundleFileSet = new BundleFileSet(instance(mockWFM)); + const bundleFileSet = new BundleFileSet( + getWorkspaceFolderManagerMock(projectDir) + ); const files = await bundleFileSet.getIncludedFiles(); expect(files?.map((f) => f.fsPath)).to.deep.equal([sharedFile]); @@ -121,13 +118,9 @@ describe(__filename, async function () { yaml.stringify(rootBundleData) ); - const mockWFM = mock(); - const mockWF = mock(); - const projectUri = Uri.file(projectDir); - when(mockWF.uri).thenReturn(projectUri); - when(mockWFM.activeWorkspaceFolder).thenReturn(instance(mockWF)); - when(mockWFM.activeProjectUri).thenReturn(projectUri); - const bundleFileSet = new BundleFileSet(instance(mockWFM)); + const bundleFileSet = new BundleFileSet( + getWorkspaceFolderManagerMock(projectDir) + ); expect( await bundleFileSet.isIncludedBundleFile(Uri.file(sharedFile)) From 1a0a399e5ea91a331bdfb0a0a4aa49ae26ba8f48 Mon Sep 17 00:00:00 2001 From: misha-db Date: Sat, 28 Mar 2026 02:05:08 +0400 Subject: [PATCH 3/3] Update BundleFileSet.test.ts --- .../databricks-vscode/src/bundle/BundleFileSet.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts b/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts index 7b4c4347b..ef5889066 100644 --- a/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts +++ b/packages/databricks-vscode/src/bundle/BundleFileSet.test.ts @@ -8,7 +8,6 @@ import {BundleSchema} from "./types"; import * as yaml from "yaml"; import {instance, mock, when} from "ts-mockito"; import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager"; -import {minimatch} from "minimatch"; describe(__filename, async function () { let tmpdir: tmp.DirectoryResult; @@ -83,10 +82,12 @@ describe(__filename, async function () { await fs.mkdir(projectDir, {recursive: true}); const sharedFile = path.join(sharedDir, "config.yml"); + const sharedFile2 = path.join(sharedDir, "config2.yml"); await fs.writeFile(sharedFile, ""); + await fs.writeFile(sharedFile2, ""); const rootBundleData: BundleSchema = { - include: ["../../shared/config.yml"], + include: ["../../shared/config.yml", "../../shared/config2.yml"], }; await fs.writeFile( path.join(projectDir, "databricks.yml"), @@ -98,7 +99,10 @@ describe(__filename, async function () { ); const files = await bundleFileSet.getIncludedFiles(); - expect(files?.map((f) => f.fsPath)).to.deep.equal([sharedFile]); + expect(files).to.not.be.undefined; + expect(files!.map((f) => f.fsPath).sort()).to.deep.equal( + [sharedFile, sharedFile2].sort() + ); }); it("isIncludedBundleFile should return true for files referenced via .. paths", async () => {