Browse Source

feat(packages): add monorepo support to package manager

- Registry now supports 'path' field for packages in monorepos
- Install function clones monorepo to cache, copies specific package
- Update function handles monorepo packages by re-syncing from cache
- Updated bundled registry to point to lumacs/packages monorepo

This allows all official packages to be hosted in a single repository
while still being installed individually.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Bernardo Magri 1 month ago
parent
commit
bcbdff2d8c
2 changed files with 163 additions and 44 deletions
  1. 91 14
      lua/core/package.lua
  2. 72 30
      lua/core/registry.json

+ 91 - 14
lua/core/package.lua

@@ -97,7 +97,7 @@ end
 -- Simple JSON parser for registry (basic implementation)
 local function parse_json_simple(str)
     -- Very basic JSON object parser for our simple registry format
-    -- Format: { "name": { "url": "...", "description": "..." }, ... }
+    -- Format: { "name": { "url": "...", "path": "...", "description": "..." }, ... }
     local result = {}
 
     -- Remove outer braces and whitespace
@@ -108,6 +108,7 @@ local function parse_json_simple(str)
     for name, content in str:gmatch('"([^"]+)"%s*:%s*({[^}]*})') do
         local entry = {}
         entry.url = content:match('"url"%s*:%s*"([^"]*)"')
+        entry.path = content:match('"path"%s*:%s*"([^"]*)"')  -- Subpath in monorepo
         entry.description = content:match('"description"%s*:%s*"([^"]*)"')
         entry.version = content:match('"version"%s*:%s*"([^"]*)"')
         result[name] = entry
@@ -288,6 +289,9 @@ end
 -- Package Installation (Git-based)
 -- ============================================================================
 
+-- Monorepo cache directory
+M.monorepo_cache = os.getenv("HOME") .. "/.lumacs/repos"
+
 -- Install a package from git
 function M.install(spec)
     local normalized = normalize_spec(spec)
@@ -297,10 +301,12 @@ function M.install(spec)
 
     local name = normalized.name
     local url = normalized.url
+    local subpath = nil  -- For monorepo packages
 
-    -- Resolve URL from registry if needed
+    -- Resolve URL and path from registry if needed
     if not url and M.registry[name] then
         url = M.registry[name].url
+        subpath = M.registry[name].path
     end
 
     if not url then
@@ -318,21 +324,60 @@ function M.install(spec)
     -- Ensure parent directory exists
     os.execute("mkdir -p " .. M.config.install_path)
 
-    -- Clone the repository
-    local branch_opt = ""
-    if normalized.branch then
-        branch_opt = " -b " .. normalized.branch
-    end
+    if subpath then
+        -- Monorepo installation: clone to cache, then copy subpath
+        local repo_name = url:match("([^/]+)$") or "repo"
+        local cache_dir = M.monorepo_cache .. "/" .. repo_name
+
+        -- Clone or update the monorepo cache
+        if not dir_exists(cache_dir) then
+            os.execute("mkdir -p " .. M.monorepo_cache)
+            local branch_opt = ""
+            if normalized.branch then
+                branch_opt = " -b " .. normalized.branch
+            end
+            local clone_cmd = string.format("git clone --depth 1%s %s %s 2>&1", branch_opt, url, cache_dir)
+            local handle = io.popen(clone_cmd)
+            local result = handle:read("*a")
+            local success = handle:close()
+            if not success then
+                return false, "Failed to clone monorepo: " .. result
+            end
+        end
 
-    local cmd = string.format("git clone --depth 1%s %s %s 2>&1", branch_opt, url, install_dir)
-    local handle = io.popen(cmd)
-    local result = handle:read("*a")
-    local success = handle:close()
+        -- Copy the specific package directory
+        local src_dir = cache_dir .. "/" .. subpath
+        if not dir_exists(src_dir) then
+            return false, "Package path not found in monorepo: " .. subpath
+        end
 
-    if success then
-        return true, "Installed " .. name
+        local copy_cmd = string.format("cp -r %s %s 2>&1", src_dir, install_dir)
+        local handle = io.popen(copy_cmd)
+        local result = handle:read("*a")
+        local success = handle:close()
+
+        if success then
+            return true, "Installed " .. name .. " (from monorepo)"
+        else
+            return false, "Failed to copy package: " .. result
+        end
     else
-        return false, "Failed to install " .. name .. ": " .. result
+        -- Standard single-repo installation
+        local branch_opt = ""
+        if normalized.branch then
+            branch_opt = " -b " .. normalized.branch
+        end
+
+        local cmd = string.format("git clone --depth 1%s %s %s 2>&1", branch_opt, url, install_dir)
+        local handle = io.popen(cmd)
+        local result = handle:read("*a")
+        local success = handle:close()
+
+        if success then
+            return true, "Installed " .. name
+        else
+            return false, "Failed to install " .. name .. ": " .. result
+        end
     end
 end
 
@@ -344,6 +389,38 @@ function M.update(name)
         return false, "Package not installed: " .. name
     end
 
+    -- Check if this is a monorepo package
+    if M.registry[name] and M.registry[name].path then
+        local subpath = M.registry[name].path
+        local url = M.registry[name].url
+        local repo_name = url:match("([^/]+)$") or "repo"
+        local cache_dir = M.monorepo_cache .. "/" .. repo_name
+
+        -- Update the monorepo cache
+        if dir_exists(cache_dir) then
+            local pull_cmd = string.format("cd %s && git pull 2>&1", cache_dir)
+            local handle = io.popen(pull_cmd)
+            handle:read("*a")
+            handle:close()
+        end
+
+        -- Re-copy the package
+        local src_dir = cache_dir .. "/" .. subpath
+        if dir_exists(src_dir) then
+            os.execute(string.format("rm -rf %s", install_dir))
+            local copy_cmd = string.format("cp -r %s %s 2>&1", src_dir, install_dir)
+            local handle = io.popen(copy_cmd)
+            local result = handle:read("*a")
+            local success = handle:close()
+            if success then
+                return true, "Updated " .. name .. " (from monorepo)"
+            else
+                return false, "Failed to update " .. name .. ": " .. result
+            end
+        end
+    end
+
+    -- Standard git pull for single-repo packages
     local cmd = string.format("cd %s && git pull 2>&1", install_dir)
     local handle = io.popen(cmd)
     local result = handle:read("*a")

+ 72 - 30
lua/core/registry.json

@@ -1,4 +1,76 @@
 {
+  "zen-mode": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "zen-mode",
+    "description": "Distraction-free writing mode with increased font size and word wrap",
+    "version": "1.0.0"
+  },
+  "which-key": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "which-key",
+    "description": "Display available keybindings when prefix key is pressed",
+    "version": "1.0.0"
+  },
+  "projectile": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "projectile",
+    "description": "Project management and navigation with file finding",
+    "version": "1.0.0"
+  },
+  "company-mode": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "company-mode",
+    "description": "Text completion framework with multiple backends",
+    "version": "1.0.0"
+  },
+  "doom-modeline": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "doom-modeline",
+    "description": "Doom Emacs-inspired modeline styling",
+    "version": "1.0.0"
+  },
+  "smartparens": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "smartparens",
+    "description": "Automatic pairing of brackets and quotes",
+    "version": "1.0.0"
+  },
+  "ido": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "ido",
+    "description": "Enhanced completion with fuzzy matching",
+    "version": "1.0.0"
+  },
+  "rainbow-delimiters": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "rainbow-delimiters",
+    "description": "Colorize nested delimiters by depth",
+    "version": "1.0.0"
+  },
+  "goto-line": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "goto-line",
+    "description": "Go to specific line number (M-g g)",
+    "version": "1.0.0"
+  },
+  "recentf": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "recentf",
+    "description": "Track and access recently opened files",
+    "version": "1.0.0"
+  },
+  "bookmarks": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "bookmarks",
+    "description": "Save and jump between named positions",
+    "version": "1.0.0"
+  },
+  "visual-line": {
+    "url": "https://github.com/lumacs/packages",
+    "path": "visual-line",
+    "description": "Soft word wrap mode",
+    "version": "1.0.0"
+  },
   "evil-mode": {
     "url": "https://github.com/lumacs/evil-mode",
     "description": "Vim emulation layer for lumacs",
@@ -9,44 +81,14 @@
     "description": "Git interface inspired by Emacs Magit",
     "version": "0.1.0"
   },
-  "treemacs": {
-    "url": "https://github.com/lumacs/treemacs",
-    "description": "File tree sidebar",
-    "version": "0.1.0"
-  },
   "lsp-mode": {
     "url": "https://github.com/lumacs/lsp-mode",
     "description": "Language Server Protocol support",
     "version": "0.1.0"
   },
-  "flycheck": {
-    "url": "https://github.com/lumacs/flycheck",
-    "description": "On-the-fly syntax checking",
-    "version": "0.1.0"
-  },
-  "dashboard": {
-    "url": "https://github.com/lumacs/dashboard",
-    "description": "Startup dashboard with recent files",
-    "version": "0.1.0"
-  },
   "org-mode": {
     "url": "https://github.com/lumacs/org-mode",
     "description": "Org-mode inspired note taking",
     "version": "0.1.0"
-  },
-  "markdown-mode": {
-    "url": "https://github.com/lumacs/markdown-mode",
-    "description": "Markdown editing support",
-    "version": "0.1.0"
-  },
-  "zen-mode": {
-    "url": "https://github.com/lumacs/zen-mode",
-    "description": "Distraction-free writing mode",
-    "version": "0.1.0"
-  },
-  "git-gutter": {
-    "url": "https://github.com/lumacs/git-gutter",
-    "description": "Show git diff in the gutter",
-    "version": "0.1.0"
   }
 }