From b1146c16f609d3e86e662b507af2dec2329f4ee7 Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Tue, 24 Feb 2026 12:04:32 +0100 Subject: [PATCH] fix: plugin sync command --- .../src/cli/commands/plugin/sync/sync.test.ts | 9 +++++ .../src/cli/commands/plugin/sync/sync.ts | 35 +++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.test.ts b/packages/shared/src/cli/commands/plugin/sync/sync.test.ts index 737dc246..5d17b62a 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.test.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.test.ts @@ -88,6 +88,15 @@ describe("plugin sync", () => { expect(imports[0].source).toBe("./plugins/my-plugin"); }); + it("extracts relative imports with file extension (.js)", () => { + const imports = parseCode( + `import { myPlugin } from "./my-plugin/my-plugin.js";`, + ); + expect(imports).toHaveLength(1); + expect(imports[0].name).toBe("myPlugin"); + expect(imports[0].source).toBe("./my-plugin/my-plugin.js"); + }); + it("handles double-quoted specifiers", () => { const imports = parseCode(`import { foo } from "@databricks/appkit";`); expect(imports[0].source).toBe("@databricks/appkit"); diff --git a/packages/shared/src/cli/commands/plugin/sync/sync.ts b/packages/shared/src/cli/commands/plugin/sync/sync.ts index 5d00b656..538cd269 100644 --- a/packages/shared/src/cli/commands/plugin/sync/sync.ts +++ b/packages/shared/src/cli/commands/plugin/sync/sync.ts @@ -210,6 +210,14 @@ function resolveLocalManifest( return null; } + // Case 0: Import path already includes an extension and resolves to a file + // e.g. ./my-plugin/my-plugin.js → ./my-plugin/manifest.json + if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) { + const dir = path.dirname(resolved); + const manifestPath = path.join(dir, "manifest.json"); + if (fs.existsSync(manifestPath)) return manifestPath; + } + // Case 1: Import path is a directory with manifest.json // e.g. ./plugins/my-plugin → ./plugins/my-plugin/manifest.json if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) { @@ -219,9 +227,13 @@ function resolveLocalManifest( // Case 2: Import path + extension resolves to a file // e.g. ./plugins/my-plugin → ./plugins/my-plugin.ts - // Look for manifest.json in the same directory + // If resolved already has an extension (e.g. .js), try base path + each ext + // so that ./my-plugin/my-plugin.js finds ./my-plugin/my-plugin.ts on disk + const basePath = path.extname(resolved).match(/^\.(js|ts|tsx|jsx)$/) + ? resolved.slice(0, -path.extname(resolved).length) + : resolved; for (const ext of RESOLVE_EXTENSIONS) { - const filePath = `${resolved}${ext}`; + const filePath = `${basePath}${ext}`; if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { const dir = path.dirname(filePath); const manifestPath = path.join(dir, "manifest.json"); @@ -561,14 +573,23 @@ function runPluginsSync(options: { let plugin: TemplatePlugin | undefined; if (isLocal) { - // Resolve the import source to an absolute path from the server file directory - const resolvedImportDir = path.resolve(serverFileDir, imp.source); + // Resolve the import source to the plugin directory for comparison. + // When the path is a file (or has an extension but file is .ts on disk), + // use its directory so ./my-plugin/my-plugin.js matches plugin at ./my-plugin + const resolvedImportPath = path.resolve(serverFileDir, imp.source); + const exists = fs.existsSync(resolvedImportPath); + const resolvedImportDir = + exists && fs.statSync(resolvedImportPath).isFile() + ? path.dirname(resolvedImportPath) + : exists && fs.statSync(resolvedImportPath).isDirectory() + ? resolvedImportPath + : path.dirname(resolvedImportPath); + // Match by directory only: one manifest per dir, and manifest.name may be + // kebab-case (e.g. "my-plugin") while the import is camelCase (myPlugin) plugin = Object.values(plugins).find((p) => { if (!p.package.startsWith(".")) return false; const resolvedPluginDir = path.resolve(cwd, p.package); - return ( - resolvedPluginDir === resolvedImportDir && p.name === imp.originalName - ); + return resolvedPluginDir === resolvedImportDir; }); } else { // npm import: direct string comparison