mirror of
https://github.com/logseq/logseq.git
synced 2026-05-29 15:09:41 +00:00
fix(electron): install CLI launcher in local bin (#12664)
* fix(electron): install CLI launcher in local bin * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
83
src/electron/electron/cli_install.cljs
Normal file
83
src/electron/electron/cli_install.cljs
Normal file
@@ -0,0 +1,83 @@
|
||||
(ns electron.cli-install
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(def CLI_LAUNCHER_MARKER "logseq-cli-managed")
|
||||
|
||||
(defn split-path-env
|
||||
[path-env windows?]
|
||||
(let [separator (if windows? ";" ":")]
|
||||
(->> (string/split (or path-env "") (re-pattern separator))
|
||||
(remove string/blank?)
|
||||
distinct)))
|
||||
|
||||
(defn preferred-unix-cli-dir
|
||||
[{:keys [home-dir path-join ensure-dir! writable-dir?]}]
|
||||
(let [user-bin (path-join home-dir ".local" "bin")]
|
||||
(ensure-dir! user-bin)
|
||||
(when (writable-dir? user-bin)
|
||||
user-bin)))
|
||||
|
||||
(defn- render-unix-cli-launcher
|
||||
[exe-path cli-path]
|
||||
(str "#!/usr/bin/env sh\n"
|
||||
"# " CLI_LAUNCHER_MARKER "\n"
|
||||
"set -eu\n"
|
||||
"ELECTRON_RUN_AS_NODE=1 exec \"" exe-path "\" \"" cli-path "\" \"$@\"\n"))
|
||||
|
||||
(defn- render-win-cli-launcher
|
||||
[exe-path cli-path]
|
||||
(str "@echo off\r\n"
|
||||
"REM " CLI_LAUNCHER_MARKER "\r\n"
|
||||
"set ELECTRON_RUN_AS_NODE=1\r\n"
|
||||
"\"" exe-path "\" \"" cli-path "\" %*\r\n"))
|
||||
|
||||
(defn- write-cli-launcher!
|
||||
[{:keys [target-path content windows? exists? read-file! write-file! chmod!]}]
|
||||
(let [should-write? (if (exists? target-path)
|
||||
(let [existing (read-file! target-path)]
|
||||
(and (string/includes? existing CLI_LAUNCHER_MARKER)
|
||||
(not= existing content)))
|
||||
true)]
|
||||
(when should-write?
|
||||
(write-file! target-path content)
|
||||
(when-not windows?
|
||||
(chmod! target-path "755"))
|
||||
true)))
|
||||
|
||||
(defn- error-message
|
||||
[error]
|
||||
(or (some-> error .-message)
|
||||
(str error)))
|
||||
|
||||
(defn install-cli-launcher!
|
||||
[{:keys [windows? cli-path cli-dir cli-dir! exe-path path-join exists? show-message-box!
|
||||
show-error-box! t log-info! log-warn!]
|
||||
:as deps}]
|
||||
(try
|
||||
(let [cli-dir (if cli-dir! (cli-dir!) cli-dir)]
|
||||
(cond
|
||||
(not (exists? cli-path))
|
||||
(throw (js/Error. (str "Missing CLI script at " cli-path)))
|
||||
|
||||
(nil? cli-dir)
|
||||
(throw (js/Error.
|
||||
(if windows?
|
||||
"No CLI install directory found. The configured install directory could not be selected or is not writable."
|
||||
"No CLI install directory found. Expected to install the launcher in ~/.local/bin, but that directory could not be created or is not writable.")))
|
||||
|
||||
:else
|
||||
(let [target-path (path-join cli-dir (if windows? "logseq.cmd" "logseq"))
|
||||
content (if windows?
|
||||
(render-win-cli-launcher exe-path cli-path)
|
||||
(render-unix-cli-launcher exe-path cli-path))
|
||||
display-dir (if windows? cli-dir "~/.local/bin")]
|
||||
(when (write-cli-launcher! (assoc deps
|
||||
:target-path target-path
|
||||
:content content))
|
||||
(log-info! :cli/install (str "Installed launcher at " target-path))
|
||||
(show-message-box! {:title "Logseq"
|
||||
:message (t :electron/cli-installed display-dir)})))))
|
||||
(catch :default error
|
||||
(let [message (error-message error)]
|
||||
(log-warn! :cli/install "Failed to install logseq launcher" error)
|
||||
(show-error-box! "Logseq" (t :electron/cli-install-failed message))))))
|
||||
@@ -7,6 +7,7 @@
|
||||
["path" :as node-path]
|
||||
[cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[electron.cli-install :as cli-install]
|
||||
[electron.db :as db]
|
||||
[electron.exceptions :as exceptions]
|
||||
[electron.handler :as handler]
|
||||
@@ -35,7 +36,6 @@
|
||||
(defonce *setup-fn (volatile! nil))
|
||||
(defonce *teardown-fn (volatile! nil))
|
||||
(defonce *quit-dirty? (volatile! true))
|
||||
(defonce CLI_LAUNCHER_MARKER "logseq-cli-managed")
|
||||
|
||||
(defn setup-updater! [^js win]
|
||||
;; manual/auto updater
|
||||
@@ -276,16 +276,6 @@
|
||||
(fn [error]
|
||||
(logger/warn :electron/wrong-release-warning-failed error))))))
|
||||
|
||||
(defn- path-separator
|
||||
[]
|
||||
(if utils/win32? ";" ":"))
|
||||
|
||||
(defn- split-path-env
|
||||
[path-env]
|
||||
(->> (string/split (or path-env "") (re-pattern (path-separator)))
|
||||
(remove string/blank?)
|
||||
distinct))
|
||||
|
||||
(defn- writable-dir?
|
||||
[dir]
|
||||
(try
|
||||
@@ -297,29 +287,27 @@
|
||||
(catch :default _
|
||||
false)))
|
||||
|
||||
(defn- find-first-writable-dir
|
||||
[dirs]
|
||||
(some #(when (writable-dir? %) %) dirs))
|
||||
|
||||
(defn- ensure-dir!
|
||||
[dir]
|
||||
(when (and (string? dir) (not (fs/existsSync dir)))
|
||||
(fs/mkdirSync dir #js {:recursive true})))
|
||||
|
||||
(defn- path-join
|
||||
[& paths]
|
||||
(apply node-path/join paths))
|
||||
|
||||
(defn- preferred-unix-cli-dir
|
||||
[]
|
||||
(let [path-dirs (split-path-env (.-PATH js/process.env))
|
||||
user-bin (node-path/join (.homedir os) ".local" "bin")]
|
||||
(or (find-first-writable-dir path-dirs)
|
||||
(do
|
||||
(ensure-dir! user-bin)
|
||||
(when (writable-dir? user-bin)
|
||||
user-bin)))))
|
||||
(cli-install/preferred-unix-cli-dir
|
||||
{:home-dir (.homedir os)
|
||||
:path-join path-join
|
||||
:ensure-dir! ensure-dir!
|
||||
:writable-dir? writable-dir?}))
|
||||
|
||||
(defn- preferred-win-cli-dir
|
||||
[]
|
||||
(let [path-env (or (.-PATH js/process.env) (.-Path js/process.env))
|
||||
path-dirs (split-path-env path-env)
|
||||
path-dirs (cli-install/split-path-env path-env utils/win32?)
|
||||
local-appdata (.-LOCALAPPDATA js/process.env)
|
||||
windows-apps-dir (when local-appdata
|
||||
(node-path/join local-appdata "Microsoft" "WindowsApps"))]
|
||||
@@ -327,7 +315,7 @@
|
||||
(ensure-dir! windows-apps-dir)
|
||||
(when (writable-dir? windows-apps-dir)
|
||||
windows-apps-dir))
|
||||
(find-first-writable-dir path-dirs))))
|
||||
(some #(when (writable-dir? %) %) path-dirs))))
|
||||
|
||||
(defn- cli-script-path
|
||||
[]
|
||||
@@ -335,59 +323,27 @@
|
||||
(node-path/join js/process.resourcesPath "app.asar" "js" "logseq-cli.js")
|
||||
(node-path/join js/__dirname "logseq-cli.js")))
|
||||
|
||||
(defn- render-unix-cli-launcher
|
||||
[exe-path cli-path]
|
||||
(str "#!/usr/bin/env sh\n"
|
||||
"# " CLI_LAUNCHER_MARKER "\n"
|
||||
"set -eu\n"
|
||||
"ELECTRON_RUN_AS_NODE=1 exec \"" exe-path "\" \"" cli-path "\" \"$@\"\n"))
|
||||
|
||||
(defn- render-win-cli-launcher
|
||||
[exe-path cli-path]
|
||||
(str "@echo off\r\n"
|
||||
"REM " CLI_LAUNCHER_MARKER "\r\n"
|
||||
"set ELECTRON_RUN_AS_NODE=1\r\n"
|
||||
"\"" exe-path "\" \"" cli-path "\" %*\r\n"))
|
||||
|
||||
(defn- write-cli-launcher!
|
||||
[path content windows?]
|
||||
(let [should-write? (if (fs/existsSync path)
|
||||
(let [existing (.readFileSync fs path "utf8")]
|
||||
(and (string/includes? existing CLI_LAUNCHER_MARKER)
|
||||
(not= existing content)))
|
||||
true)]
|
||||
(when should-write?
|
||||
(.writeFileSync fs path content "utf8")
|
||||
(when-not windows?
|
||||
(fs/chmodSync path "755"))
|
||||
true)))
|
||||
|
||||
(defn- install-cli-launcher!
|
||||
[]
|
||||
(try
|
||||
(let [cli-path (cli-script-path)
|
||||
cli-dir (if utils/win32?
|
||||
(let [cli-path (cli-script-path)
|
||||
cli-dir! #(if utils/win32?
|
||||
(preferred-win-cli-dir)
|
||||
(preferred-unix-cli-dir))]
|
||||
(cond
|
||||
(not (fs/existsSync cli-path))
|
||||
(logger/warn :cli/install (str "Missing CLI script at " cli-path ", skip installing launcher"))
|
||||
|
||||
(nil? cli-dir)
|
||||
(logger/warn :cli/install "No writable PATH directory found; skip installing logseq launcher")
|
||||
|
||||
:else
|
||||
(let [target-path (if utils/win32?
|
||||
(node-path/join cli-dir "logseq.cmd")
|
||||
(node-path/join cli-dir "logseq"))
|
||||
exe-path (.getPath app "exe")
|
||||
content (if utils/win32?
|
||||
(render-win-cli-launcher exe-path cli-path)
|
||||
(render-unix-cli-launcher exe-path cli-path))]
|
||||
(when (write-cli-launcher! target-path content utils/win32?)
|
||||
(logger/info :cli/install (str "Installed launcher at " target-path))))))
|
||||
(catch :default e
|
||||
(logger/warn :cli/install "Failed to install logseq launcher" e))))
|
||||
(cli-install/install-cli-launcher!
|
||||
{:windows? utils/win32?
|
||||
:cli-path cli-path
|
||||
:cli-dir! cli-dir!
|
||||
:exe-path (.getPath app "exe")
|
||||
:path-join path-join
|
||||
:exists? #(fs/existsSync %)
|
||||
:read-file! #(.readFileSync fs % "utf8")
|
||||
:write-file! #(.writeFileSync fs %1 %2 "utf8")
|
||||
:chmod! #(fs/chmodSync %1 %2)
|
||||
:show-message-box! #(.showMessageBox dialog (clj->js %))
|
||||
:show-error-box! #(.showErrorBox dialog %1 %2)
|
||||
:t t
|
||||
:log-info! logger/info
|
||||
:log-warn! logger/warn})))
|
||||
|
||||
(defn- on-app-ready!
|
||||
[^js app']
|
||||
|
||||
@@ -529,6 +529,8 @@
|
||||
:electron/add-to-dictionary "Add to dictionary"
|
||||
:electron/block-not-exist "Open link failed. Block-id `{1}` doesn't exist in the graph."
|
||||
:electron/cancel "Cancel"
|
||||
:electron/cli-install-failed "Failed to install Logseq CLI.\n{1}"
|
||||
:electron/cli-installed "Logseq CLI was installed to {1}"
|
||||
:electron/copy-image "Copy Image"
|
||||
:electron/link-open-confirm "Are you sure you want to open this link? \n{1}"
|
||||
:electron/link-open-failed-missing-graph "Failed to open link. Missing graph identifier after `logseq://graph/`."
|
||||
|
||||
@@ -526,6 +526,8 @@
|
||||
:electron/add-to-dictionary "添加到字典"
|
||||
:electron/block-not-exist "打开链接失败。块 ID `{1}` 在当前图谱中不存在。"
|
||||
:electron/cancel "取消"
|
||||
:electron/cli-install-failed "安装 Logseq CLI 失败。\n{1}"
|
||||
:electron/cli-installed "Logseq CLI 已安装到 {1}"
|
||||
:electron/copy-image "复制图片"
|
||||
:electron/link-open-confirm "确定要打开此链接吗?\n{1}"
|
||||
:electron/link-open-failed-missing-graph "打开链接失败。在 `logseq://graph/` 后缺少图谱标识符。"
|
||||
|
||||
105
src/test/electron/cli_install_test.cljs
Normal file
105
src/test/electron/cli_install_test.cljs
Normal file
@@ -0,0 +1,105 @@
|
||||
(ns electron.cli-install-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[clojure.string :as string]
|
||||
[electron.cli-install :as cli-install]))
|
||||
|
||||
(defn- path-join
|
||||
[& parts]
|
||||
(string/join "/" parts))
|
||||
|
||||
(defn- t
|
||||
[k & args]
|
||||
(case k
|
||||
:electron/cli-installed (str "Logseq CLI was installed to " (first args))
|
||||
:electron/cli-install-failed (str "Failed to install Logseq CLI.\n" (first args))))
|
||||
|
||||
(deftest preferred-unix-cli-dir-prefers-local-bin
|
||||
(testing "macOS/Linux CLI launcher directory is ~/.local/bin, not the first writable PATH directory"
|
||||
(let [created (atom [])]
|
||||
(is (= "/home/me/.local/bin"
|
||||
(cli-install/preferred-unix-cli-dir
|
||||
{:home-dir "/home/me"
|
||||
:path-join path-join
|
||||
:ensure-dir! #(swap! created conj %)
|
||||
:writable-dir? (fn [dir]
|
||||
(#{"first-writable-path-dir" "/home/me/.local/bin"} dir))})))
|
||||
(is (= ["/home/me/.local/bin"] @created)))))
|
||||
|
||||
(defn- run-install!
|
||||
[opts]
|
||||
(let [writes (atom [])
|
||||
chmods (atom [])
|
||||
messages (atom [])
|
||||
errors (atom [])
|
||||
files (atom (set (:existing-files opts)))
|
||||
contents (atom (:existing-contents opts))]
|
||||
(cli-install/install-cli-launcher!
|
||||
(merge
|
||||
{:windows? false
|
||||
:cli-path "/app/logseq-cli.js"
|
||||
:cli-dir "/home/me/.local/bin"
|
||||
:exe-path "/Applications/Logseq.app/Contents/MacOS/Logseq"
|
||||
:path-join path-join
|
||||
:exists? #(contains? @files %)
|
||||
:read-file! #(get @contents %)
|
||||
:write-file! (fn [path content]
|
||||
(swap! writes conj [path content])
|
||||
(swap! files conj path))
|
||||
:chmod! #(swap! chmods conj [%1 %2])
|
||||
:show-message-box! #(swap! messages conj %)
|
||||
:show-error-box! (fn [title content]
|
||||
(swap! errors conj {:title title :content content}))
|
||||
:t t
|
||||
:log-info! (fn [& _])
|
||||
:log-warn! (fn [& _])}
|
||||
opts))
|
||||
{:writes @writes
|
||||
:chmods @chmods
|
||||
:messages @messages
|
||||
:errors @errors}))
|
||||
|
||||
(deftest install-cli-launcher-shows-success-dialog
|
||||
(testing "successful Unix install writes to ~/.local/bin and reports the user-facing directory"
|
||||
(let [result (run-install! {:existing-files #{"/app/logseq-cli.js"}})]
|
||||
(is (= "/home/me/.local/bin/logseq" (ffirst (:writes result))))
|
||||
(is (= [["/home/me/.local/bin/logseq" "755"]] (:chmods result)))
|
||||
(is (= [] (:errors result)))
|
||||
(is (= [{:title "Logseq"
|
||||
:message "Logseq CLI was installed to ~/.local/bin"}]
|
||||
(:messages result))))))
|
||||
|
||||
(deftest install-cli-launcher-keeps-windows-path
|
||||
(testing "Windows keeps the existing Windows install path behavior and reports that directory"
|
||||
(let [windows-dir "C:/Users/me/AppData/Local/Microsoft/WindowsApps"
|
||||
result (run-install! {:windows? true
|
||||
:cli-dir windows-dir
|
||||
:existing-files #{"/app/logseq-cli.js"}})]
|
||||
(is (= (str windows-dir "/logseq.cmd") (ffirst (:writes result))))
|
||||
(is (= [] (:chmods result)))
|
||||
(is (= [] (:errors result)))
|
||||
(is (= [{:title "Logseq"
|
||||
:message (str "Logseq CLI was installed to " windows-dir)}]
|
||||
(:messages result))))))
|
||||
|
||||
(deftest install-cli-launcher-shows-error-dialog-on-failure
|
||||
(testing "installer failures are visible through an Electron error dialog"
|
||||
(let [result (run-install! {:existing-files #{"/app/logseq-cli.js"}
|
||||
:write-file! (fn [& _]
|
||||
(throw (js/Error. "disk full")))})]
|
||||
(is (= [] (:messages result)))
|
||||
(is (= "Logseq" (:title (first (:errors result)))))
|
||||
(is (string/includes? (:content (first (:errors result)))
|
||||
"Failed to install Logseq CLI"))
|
||||
(is (string/includes? (:content (first (:errors result)))
|
||||
"disk full")))))
|
||||
|
||||
(deftest install-cli-launcher-shows-error-dialog-when-directory-selection-fails
|
||||
(testing "directory selection failures are visible through an Electron error dialog"
|
||||
(let [result (run-install! {:existing-files #{"/app/logseq-cli.js"}
|
||||
:cli-dir nil
|
||||
:cli-dir! (fn []
|
||||
(throw (js/Error. "permission denied")))})]
|
||||
(is (= [] (:messages result)))
|
||||
(is (= "Logseq" (:title (first (:errors result)))))
|
||||
(is (string/includes? (:content (first (:errors result)))
|
||||
"permission denied")))))
|
||||
Reference in New Issue
Block a user