[space-lua] Command to update silverbullet-libraries

Here is my first attempt to make an Update Library command for your imported libraries, for the currently opened page.

Like I mentioned in the General chat: this is my first attempt, and the error check is not that complex. it only does some basic safety checks.
How it currently works:

  • Gets the current page and reads its githubUrl from frontmatter.
  • Converts the GitHub link to a raw URL.
  • Fetches the latest file content from GitHub.
  • Replaces the page content (adding the URL back to the frontmatter).
  • Saves and reloads the updated page.
command.define {
  name = "Import: Update library for current page from GitHub",
  key = "Ctrl-Alt-u",
  mac = "Cmd-Alt-u",
  run = function()
    local page = editor.getCurrentPage()
    if not page then
      editor.flashNotification("No page open to update", "error")
      return
    end

 -- get current text and original frontmatter block (raw)
    local original_text = editor.getText() or ""

    local parsed = index.extractFrontmatter(original_text)
    local fm = parsed.frontmatter or {}
    local url = fm.githubUrl
    local frontmatter = '---\ngithubUrl: "'.. url ..'"\n---'
    
    if not url or url == "" then
      editor.flashNotification("⚠️ No 'githubUrl' found in frontmatter", "error")
      return
    end

    -- Convert GitHub URL to raw URL
    local rawUrl = url
    rawUrl = rawUrl:gsub("^https://github%.com/", "https://raw.githubusercontent.com/")
    rawUrl = rawUrl:gsub("/blob/", "/")
    rawUrl = rawUrl:gsub("/tree/", "/")

    editor.flashNotification("Fetching latest version from GitHub…")

    local req = http.request(rawUrl)
    if not req.ok then
      js.log("Failed to fetch " .. rawUrl)
      editor.flashNotification("⚠️ Update failed: could not fetch remote file", "error")
      return
    end

    local newContent = req.body or ""
    if newContent == "" then
      editor.flashNotification("⚠️ Fetched content is empty.", "error")
      return
    end

    local final = nil
    if fm and fm ~= "" then
      final = frontmatter .. "\n\n" .. newContent
    else
      final = newContent
    end

    -- write back and navigate
    space.writePage(page, final)
    editor.flashNotification("âś… Page updated from GitHub")
    editor.navigate({ kind = "page", page = page })
  end
}

It will do following safety checks:

  • Checks if a page is open (duh).
  • Verifies that githubUrl exists in the frontmatter.
  • Confirms the HTTP request succeeds.
  • Ensures the fetched content is not empty.

The script is still in development so use it at your own risk.
When I will have a foolproof version, will add it to my library, until then feel free to improve or give feedback.

2 Likes

And here it is in action:

I deleted a bunch of code and text from one of my libraries. Then after I ran the command, everything automagically reappeared:

screencast

Thanks for your reactivity. Could you include It to your libraries ?

I will do, but currently it’s a little raw…

I would like to add some interactivity to it with the options to chose between:

Update current page only …
Update all custom libraries …
List Custom/Imported Libraries …
|-> Open the selected Page
|-> Update selected Page

that’s the plan at least. but i dunno if i can do all this alone :stuck_out_tongue:
but good that i have ChatGPT on my side :wink:

This is a complete Solution for importing and Managing your Silvernbullet-Libraries
The “old” “UpdateSilverbulletLibraries.md” is deprecated and therefor it will be not further updated.

Here is the new Version

And here some screenshots:

Have it running! It’s failing when you use an invalid Github token I found, but seeing how SilverBullet can give a better error when that happens (I had to debug this by adding a bunch of print statements…).

1 Like

Currently it’s either NO TOKEN at all OR you need to supply a Correct Personal Access Token (it’s enough a read only token, with no permission whatsoever if someone is paranoid), i will provide link and instructions on how to get such a token.

Also I can add later some sanity checks and fallback to NO-Autherntication if incorrect TOKEN is used, or user warning to add/correct Github API Token, or simply tell the user to wait 1 hour & try again if the 60 requests are used up.

Fixed it, but now I need get home, and will push it ASAP.

Nice. In the mean time I just pushed a fix that will actually jump to the source location of a Lua error when it occurs (in many cases). Try this (once the new build goes live):

command.define {
  name = "Explode",
  run = function()
    editor.flashNotification "Hello world!"
    error("BOOM")
  end
}

My Windows Defender doesn’t like the new edge build (flagged as Trojan again)…need to wait to get home to try it on my linux machine.

No comment

1 Like

Pushed the bugfix!

Hit the green [Update]-button in the Library Manager to update itself :wink:

:warning: The config variable has changed from apiKey to apiToken, matching GitHub’s terminology.

1 Like

Really liking this. Wonder how to build these things generally. It’s kind of messy to have the UI and its implementation mixed in one page… On the other side, the whole thing is optimized to share single page “apps” :thinking:

Thank you :folded_hands:!

You can always copy/paste the table and the buttons on any new page, so you’ve separated the “UI” from the code:

The only downside is you need to manually Copy/Paste it again if some major breaking changes happen.

BTW I tried it and it workzzz

it jumped straight to the error("BOOM") line even if I were on a complete other page! Love this one!

1 Like

Alternatively you could create a command to Install UI, which will inject the UI Page contents into a new file with the push of a button, and the UI is “installed” in a separate page:

So the user has minimal maintenance to do.

Here the code to copy/paste.

local initUI = [[

${widgets.commandButton("Import: URL")} | ${widgets.commandButton("Import: Browse GitHub Repositories")} | ${widgets.commandButton("Update All Github Libraries","Import: Check & Update All GitHub Libraries")} | ${widgets.commandButton("Update All Markdown Libraries","Import: Update All Raw-Markdown Libraries")} | ${widgets.commandButton("Import: Check All Github Update Statuses")}

# 📥 Imported Silverbullet-Libraries

${buildTable(query[[from index.tag "page" where githubUrl != nil or source == "markdown-import" 
select {
  ref = ref,
  maintainer = (githubUrl and githubUrl:match("github%.com/([^/]+)/")) or "",
  githubUrl = githubUrl,
  sourceUrl = sourceUrl,
  status = (
      (not updateDate or updateDate == "" or updateDate == nil  
      or not lastCommitDate or lastCommitDate == "" or lastCommitDate == nil ) and "⚪ Unknown"
      or updateDate < lastCommitDate and "🔴 Outdated" or "🟢 Up to date"),
   last_updated = (updateDate and parse_datetime(updateDate)) 
    and os.date("%y.%m.%d %H:%M", parse_datetime(updateDate)) or "",
   last_commit = (lastCommitDate and parse_datetime(lastCommitDate)) 
    and os.date("%y.%m.%d %H:%M", parse_datetime(lastCommitDate)) or ""
  }
order by updateDate]] .. "]])}" 

command.define {
  name = "Install ManageLibraries UI",
  hide = true,
  run = function()
          local UIFile = space.writeDocument("Library/ManageLibraries.md", initUI)
          editor.flashNotification("UI-Page was saved with size: " .. UIFile.size .. " bytes")
          editor.navigate("Library/ManageLibraries")
    end
}

I already did this to inject the .js from a markdown/space-lua into a new file in my Treeview_Extension_DragAndResize library.

I think maybe this can be exposed as a virtualPage under e.g. library:manager or something. Makes sense to make it read only anyway. :thinking:

I didn’t dive so deep into virtual pages yet. but I guess it can and it definitely should be Read Only.

Ok, just a head’s up I got a very exciting idea regarding repository management and unifying libraries and plugs this way. I’ll be spending my Friday on this, I’ll post an update later. Thanks a lot for your work and ideas here @Mr.Red this is helping a lot in solving this problem finally.

1 Like

Sounds promising!
Have fun & a productive day.

1 Like

Update: made a lot of progress, but not there yet. High level what I’m working on:

I’m introducing concept of a repository, in effect nothing more than a regular silverbullet page with #meta/library/remote data items.

These can be space lua libraries (that need in turn need to be tagged with #meta/library to enable updating), but also “plug wrappers”:

```#meta/library/remote
# Example of a space lua library
name: Git
uri: github:zefhemel/silverbullet-libraries/Git.md
---
name: Ghost
uri: github:zefhemel/silverbullet-libraries/Ghost.md
---
# This is a plug "wrapper"
name: Mermaid
uri: github:silverbulletmd/silverbullet-mermaid/README.md
---
name: Silversearch
uri: ghr:zefhemel/silversearch@edge/README.md
---
# And would be the repo itself (this very page)
name: REPO
uri: github:zefhemel/silverbullet-libraries/REPO.md
```

A linked page can contain additional assets (defined in front matter), such as a plug’s JS file that will be downloaded and placed into your space during install/update, see silverbullet-mermaid/README.md at main · silverbulletmd/silverbullet-mermaid · GitHub for an example.

Then there are broadly speaking the following commands:

  • Library: Install which will give you a filter-box style UI to pick the library/plug (encoded as #meta/library/remote objects) you’d like to install.
  • Library: Update which will look at all the currently installed libraries (tagged with #meta/library), find their remote counter part, compare versions and if they’re different, upgrade
  • Library: Delete to delete a library (and its assets)
  • Repo: Add to add a repo (based on some URI), which in effect would simply import

Anyway, still ironing out the details. Next week, probably.

3 Likes