diff options
-rw-r--r-- | .github/workflows/build.yml | 11 | ||||
-rw-r--r-- | README.md | 17 | ||||
-rw-r--r-- | lpm.lua | 146 | ||||
-rw-r--r-- | plugins/plugin_manager/init.lua | 3 |
4 files changed, 113 insertions, 64 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d7c466..98b11ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,5 +31,12 @@ jobs: env: { GITHUB_TOKEN: "${{ github.token }}" } run: | gh release delete -y continuous || true; gh release create -t 'Continuous Release' continuous *.zip *.tar.gz *.deb - [[ `git describe HEAD --tags --match=v* | head -c 4` == "v"* ]] && gh release delete -y `git describe HEAD --tags --match=v* | head -c 4` || true; - [[ `git describe HEAD --tags --match=v* | head -c 4` == "v"* ]] && gh release create -t `git describe HEAD --tags --match=v* | head -c 4` `git describe HEAD --tags --match=v* | head -c 4` lpm.x86_64-linux lpm.x86_64-windows.exe + if [[ `git tag --points-at HEAD | head -c 4` == "v"* ]]; then + export RELEASE=`git tag --points-at HEAD | head -c 4` + gh release delete -y $RELEASE || true; + gh release create -t $RELEASE $RELEASE lpm.x86_64-linux lpm.x86_64-windows.exe + git checkout master && cat manifest.json | jq ".plugins[0].files[0].checksum = "'"'"`sha256sum lpm.x86_64-linux | sed 's/ .*//'`"'"' | jq ".plugins[0].files[1].checksum = "'"'"`sha256sum lpm.x86_64-windows.exe | sed 's/ .*//'`"'"' > new-manifest.json + mv new-manifest.json manifest.json && git add manifest.json && git commit -m 'Updated manifest.json with updated release checksums.' && git push + fi + + @@ -26,6 +26,18 @@ If you want to build it quickly, and have the right modules installed, you can d CI is enabled on this repository, so you can grab Windows and Linux builds from the `continuous` [release page](https://github.com/adamharrison/lite-xl-plugin-manager/releases/tag/continuous). +## Use in CI + +To make pre-fab lite builds, you can easily use `lpm` in CI. If you had a linux build container, you could do something like: + +```sh + +curl https://github.com/adamharrison/lite-xl-plugin-manager/releases/download/v0.1/lpm.x86_64-linux > lpm +export LITE_USERDIR=lite-xl/data && export LPM_CACHE=/tmp/cache +./lpm add https://github.com/adamharrison/lite-xl-plugin-manager && ./lpm install plugin_manager lsp + +``` + ## Usage ```sh @@ -33,8 +45,9 @@ CI is enabled on this repository, so you can grab Windows and Linux builds from lpm update && lpm install aligncarets lpm uninstall aligncarets -lpm add https://github.com/lite-xl/lite-xl-plugins.git -lpm rm https://github.com/lite-xl/lite-xl-plugins.git +lpm add https://github.com/adamharrison/lite-xl-plugin-manager.git +lpm install plugin_manager +lpm rm https://github.com/adamharrison/lite-xl-plugin-manager.git ``` @@ -459,7 +459,7 @@ local function is_commit_hash(hash) end -local HOME, USERDIR, CACHEDIR, JSON, VERBOSE, MOD_VERSION, QUIET, FORCE, AUTO_PULL_REMOTES, ARCH, ASSUME_YES, INSTALL_OPTIONAL, repositories +local HOME, USERDIR, CACHEDIR, JSON, VERBOSE, MOD_VERSION, QUIET, FORCE, AUTO_PULL_REMOTES, ARCH, ASSUME_YES, NO_INSTALL_OPTIONAL, TMPDIR, repositories local actions, warnings = {}, {} local function log_action(message) @@ -480,14 +480,30 @@ local function prompt(message) return not response:find("%S") or response:find("^%s*[yY]%s*$") end +local function compare_version(a, b) -- compares semver + if not a or not b then return false end + local _, _, majora, minora, revisiona = a:find("(%d+)%.?(%d*)%.?(%d*)") + local _, _, majorb, minorb, revisionb = b:find("(%d+)%.?(%d*)%.?(%d*)") + if majora == nil then error("can't parse version " .. a) end + if majorb == nil then error("can't parse version " .. b) end + majora, minora, revisiona = majora or 0, minora or 0, revisiona or 0 + majorb, minorb, revisionb = majorb or 0, minorb or 0, revisionb or 0 + if majora ~= majorb then return tonumber(majora) < tonumber(majorb) and -1 or 1 end + if minora ~= minorb then return tonumber(minora) < tonumber(minorb) and -1 or 1 end + if revisiona ~= revisionb then return tonumber(revisiona) < tonumber(revisionb) and -1 or 1 end + return 0 +end local function match_version(version, pattern) - return not pattern or version == pattern + if not pattern then return true end + if pattern:find("^>=") then return compare_version(version, pattern:sub(3)) >= 0 end + if pattern:find("^<=") then return compare_version(version, pattern:sub(3)) <= 0 end + if pattern:find("^<") then return compare_version(version, pattern:sub(2)) == -1 end + if pattern:find("^>") then return compare_version(version, pattern:sub(2)) == 1 end + if pattern:find("^=") then return compare_version(version, pattern:sub(2)) == 0 end + return version == pattern end -local function compare_version(a, b) - return a and b and tonumber(a) < tonumber(b) -end local function get_all_plugins() local t = {} @@ -610,6 +626,7 @@ local core_plugins = { function Plugin:install(installing) if self.status == "installed" then error("plugin " .. self.name .. " is already installed") end + local temporary_install_path = TMPDIR .. self.install_path:sub(#CACHEDIR) local status, err = pcall(function() installing = installing or {} installing[self.name] = true @@ -618,19 +635,19 @@ function Plugin:install(installing) if incompatible[plugin] then error("can't install " .. self.name .. ": incompatible with " .. incompatible[plugin][1].name .. ":" .. incompatible[plugin][1].version) end end for plugin, v in pairs(self.dependencies) do - if not core_plugins[plugin] and not compatible[plugin] then error("can't find dependency " .. plugin .. (v.version and (":" .. version) or "")) end + if not core_plugins[plugin] and not compatible[plugin] then error("can't find dependency " .. plugin .. (v.version and (":" .. v.version) or "")) end end for plugin, v in pairs(self.dependencies) do if not core_plugins[plugin] and not compatible[plugin]:is_installed() then if installing[plugin] then error("circular dependency detected in " .. self.name .. ": requires " .. plugin .. " but, " .. plugin .. " requires " .. self.name) end - if not v.optional or prompt(plugin .. " is an optional dependency of " .. self.name .. ". Should we install it?") then + if not NO_INSTALL_OPTIONAL and (not v.optional or prompt(plugin .. " is an optional dependency of " .. self.name .. ". Should we install it?")) then compatible[plugin]:install(installing) end end end - common.mkdirp(common.dirname(self.install_path)) + common.mkdirp(common.dirname(temporary_install_path)) if self.status == "upgradable" then log_action("Upgrading " .. self.organization .. "plugin located at " .. self.local_path .. " to " .. self.install_path) common.rmrf(self.install_path) @@ -641,41 +658,48 @@ function Plugin:install(installing) if self.organization == "complex" and self.path and system.stat(self.local_path).type ~= "dir" then common.mkdirp(self.install_path) end if self.url then log_action("Downloading file " .. self.url .. "...") - local path = self.install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") + local path = temporary_install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") system.get(self.url, path) log_action("Downloaded file " .. self.url .. " to " .. path) if system.hash(path, "file") ~= self.checksum then fatal_warning("checksum doesn't match for " .. path) end elseif self.remote then log_action("Cloning repository " .. self.remote .. " into " .. self.install_path) - common.mkdirp(self.install_path) + common.mkdirp(temporary_install_path) local _, _, url, branch = self.remote:find("^(.*):(.*)$") - system.init(self.install_path, url) - system.reset(self.install_path, branch) + system.init(temporary_install_path, url) + system.reset(temporary_install_path, branch) else local path = self.install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") + local temporary_path = temporary_install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") log_action("Copying " .. self.local_path .. " to " .. path) - common.copy(self.local_path, path) + common.copy(self.local_path, temporary_path) end for i,file in ipairs(self.files or {}) do if not file.arch or file.arch == ARCH then - if not file.checksum then error("requires a checksum") end - local path = self.install_path .. PATHSEP .. (file.path or common.basename(file.url)) - log_action("Downloading file " .. file.url .. "...") - system.get(file.url, path) - log_action("Downloaded file " .. file.url .. " to " .. path) - if system.hash(path, "file") ~= file.checksum then fatal_warning("checksum doesn't match for " .. path) end + if not NO_INSTALL_OPTIONAL and (not file.optional or prompt(common.basename(file.url) .. " is an optional dependency of " .. self.name .. ". Should we install it?")) then + if not file.checksum then error("requires a checksum") end + local path = self.install_path .. PATHSEP .. (file.path or common.basename(file.url)) + local temporary_path = temporary_install_path .. PATHSEP .. (file.path or common.basename(file.url)) + log_action("Downloading file " .. file.url .. "...") + system.get(file.url, temporary_path) + log_action("Downloaded file " .. file.url .. " to " .. path) + if system.hash(temporary_path, "file") ~= file.checksum then fatal_warning("checksum doesn't match for " .. path) end + end end end end) if not status then - common.rmrf(self.install_path) + common.rmrf(temporary_install_path) error(err) + else + common.rmrf(self.install_path) + os.rename(temporary_install_path, self.install_path) end end function Plugin:depends_on(plugin) - if self.dependencies[plugin] and self.dependencies[plugin].optional ~= false then return true end - for i,v in ipairs(plugin.provides or {}) do if self.dependencies[v] and self.dependencies[v].optional ~= false then return true end end + if self.dependencies[plugin.name] and self.dependencies[plugin.name].optional ~= true then return true end + for i,v in ipairs(plugin.provides or {}) do if self.dependencies[v] and self.dependencies[v].optional ~= true then return true end end return false end @@ -1118,10 +1142,10 @@ xpcall(function() end if ARGS["help"] or #ARGS == 1 or ARGS[2] == "help" then io.stderr:write([[ -Usage: lpm COMMAND [--json] [--userdir=directory] [--cachedir=directory] - [--verbose] [--mod-version=3] [--quiet] [--version] [--help] [--remotes] +Usage: lpm COMMAND [...ARGUMENTS] [--json] [--userdir=directory] + [--cachedir=directory] [--quiet] [--version] [--help] [--remotes] [--ssl_certs=directory/file] [--force] [--arch=]] .. _G.ARCH .. [[] - [--assume-yes] [--install-optional] + [--assume-yes] [--no-install-optional] [--verbose] [--mod-version=3] LPM is a package manager for `lite-xl`, written in C (and packed-in lua). @@ -1135,46 +1159,51 @@ but others can be added, and this base one can be removed. It has the following commands: - lpm repo list List all extant repos. - lpm [repo] add <repository remote> Add a source repository. + lpm repo list List all extant repos. + lpm [repo] add <repository remote> Add a source repository. [...<repository remote>] - lpm [repo] rm <repository remote> Remove a source repository. + lpm [repo] rm <repository remote> Remove a source repository. [...<repository remote>] - lpm [repo] update [<repository remote>] Update all/the specified repositories. + lpm [repo] update [<repository remote>] Update all/the specified repos. [...<repository remote>] - lpm [plugin] install <plugin name>[:<version>] Install the specific plugins in question. - [...<plugin name>:<version>] If the plugin is installed, upgrades it. - lpm [plugin] uninstall <plugin name> Uninstall the specific plugin. + lpm [plugin] install Install specific plugins. + <plugin name>[:<version>] If installed, upgrades. + [...<plugin name>:<version>] + lpm [plugin] uninstall <plugin name> Uninstall the specific plugin. [...<plugin name>] - lpm [plugin] list <repository remote> List all/associated plugins. + lpm [plugin] list <repository remote> List all/associated plugins. [...<repository remote>] - lpm [plugin] upgrade Upgrades all installed plugins to new version - if applicable. - lpm purge Completely purge all state for LPM. - lpm - Read these commands from stdin in an - interactive print-eval loop. - lpm help Displays this help text. + lpm [plugin] upgrade Upgrades all installed plugins + to new version if applicable. + lpm purge Completely purge all state for LPM. + lpm - Read these commands from stdin in + an interactive print-eval loop. + lpm help Displays this help text. Flags have the following effects: - --json Performs all communication in JSON, rather than human-readable. - --userdir=directory Treats the specified directory as the lite-xl userdir. - By default, this is set based on the normal lite-xl logic. - --cachedir=directory Sets the directory to store all repositories. - --verbose Spits out more information, including intermediate steps to - install and whatnot. - --quiet Outputs nothing but explicit returning information from actions. - --modversion=3 Sets the mod version of lite-xl to install plugins against. - --version Returns version information. - --remotes Automatically adds any specified remotes in the repository to - the end of the resolution list. - --help Displays this help text. - --ssl_certs Sets the SSL certificate store to specified location. - --arch Overrides the inferred architecture (default: ]] .. _G.ARCH .. [[). - --force Ignores checksum inconsitencies. Not recommended. - --assume-yes Ignores any prompts, and automatically answers yes to all. - --install-optional On install, any dependencies marked as optional that can be installed - will be installled. + --json Performs all communication in JSON. + --userdir=directory Sets the lite-xl userdir manually. + If omitted, uses the normal lite-xl logic. + --cachedir=directory Sets the directory to store all repositories. + --tmpdir=directory During install, sets the staging area. + --verbose Spits out more information, including intermediate + steps to install and whatnot. + --quiet Outputs nothing but explicit responses. + --modversion=3 Sets the mod version of lite-xl to install plugins. + --version Returns version information. + --remotes Automatically adds any specified remotes in the + repository to the end of the resolution list. + This is a potential security risk, so be careful. + --help Displays this help text. + --ssl_certs Sets the SSL certificate store. + --arch Sets the architecture (default: ]] .. _G.ARCH .. [[). + --force Ignores checksum inconsitencies. + Not recommended; security risk. + --assume-yes Ignores any prompts, and automatically answers yes + to all. + --no-install-optional On install, anything marked as optional + won't prompt. ]] ) return 0 @@ -1184,7 +1213,7 @@ Flags have the following effects: JSON = ARGS["json"] or os.getenv("LPM_JSON") QUIET = ARGS["quiet"] or os.getenv("LPM_QUIET") FORCE = ARGS["force"] - INSTALL_OPTIONAL = ARGS["install-optional"] + NO_INSTALL_OPTIONAL = ARGS["no-install-optional"] ARCH = ARGS["arch"] or _G.ARCH ASSUME_YES = ARGS["assume-yes"] or FORCE MOD_VERSION = ARGS["mod-version"] or os.getenv("LPM_MODVERSION") or 3 @@ -1195,6 +1224,7 @@ Flags have the following effects: AUTO_PULL_REMOTES = ARGS["remotes"] if not system.stat(USERDIR) then error("can't find user directory " .. USERDIR) end CACHEDIR = ARGS["cachedir"] or os.getenv("LPM_CACHE") or USERDIR .. PATHSEP .. "lpm" + TMPDIR = ARGS["tmpdir"] or CACHEDIR .. "/tmp" if ARGS[2] == "purge" then return lpm_purge() end if ARGS["ssl_certs"] then diff --git a/plugins/plugin_manager/init.lua b/plugins/plugin_manager/init.lua index 566ba8b..c062335 100644 --- a/plugins/plugin_manager/init.lua +++ b/plugins/plugin_manager/init.lua @@ -66,14 +66,13 @@ function Promise:forward(promise) self:done(function(data) promise:resolve(data) local running_processes = {} local function run(cmd) - local t = { config.plugins.plugin_manager.lpm, table.unpack(cmd), "--json", "--mod-version", MOD_VERSION } - if config.plugins.plugin_manager.ssl_certs then table.insert(t, "--ssl_certs") table.insert(t, config.plugins.plugin_manager.ssl_certs) end table.insert(cmd, 1, config.plugins.plugin_manager.lpm_binary_path) table.insert(cmd, "--json") table.insert(cmd, "--mod-version=" .. MOD_VERSION) table.insert(cmd, "--quiet") table.insert(cmd, "--userdir=" .. USERDIR) table.insert(cmd, "--assume-yes") + if config.plugins.plugin_manager.ssl_certs then table.insert(cmd, "--ssl_certs") table.insert(cmd, config.plugins.plugin_manager.ssl_certs) end if config.plugins.plugin_manager.force then table.insert(cmd, "--force") end local proc = process.start(cmd) if config.plugins.plugin_manager.debug then for i, v in ipairs(cmd) do io.stdout:write((i > 1 and " " or "") .. v) end io.stdout:write("\n") io.stdout:flush() end |