Gist Desktop Apps
Sharp Apps can also be published to Gists where they can be run on-the-fly without installation, they're always up-to-date, have tiny footprints that are fast to download and launch, that can also run locally, off-line and cross-platform across Windows, macOS and Linux OS's.
To recap Sharp Apps enable a dramatically simplified and productive workflow where entire Apps can be developed in a real-time without compilation using a JavaScript inspired syntax and access to a comprehensive default library including built-in support for the most popular RDBMS's, Redis, AWS, and Azure that can be further extended with support for plugins.
Windows .NET Core Desktop Apps
Major disadvantages to developing Desktop Apps today include limitations in available UI frameworks not being as flexible and feature-rich as modern browser rendering engines, slow development/iteration times, large downloads, forced upfront installations, stale versions, and cumbersome upgrades - resulting in both increased cost to develop Desktop Apps and reduced accessibility and potential popularity with the additional barrier to entry of forced installations.
Electron resolves some of these issues and has seen a surge of popularity vs Native Apps with its more productive web development model and partial support for auto updating, but it still requires a large download and upfront installation.
Tiny Footprint
By contrast, the majority of the download size of Sharp Apps is in the local .NET Core installation and app
dotnet tool which are shared by all Sharp Apps, only the app-specific web assets and #Script
source files need to be downloaded, making
them a lot smaller and quicker to download (and run instantly).
They can even be further reduced by utilizing the resources embedded into ServiceStack like the built-in
SVG images and stylesheet and /css/bootstrap.css
, which many Sharp Apps take advantage of to reduce their footprint.
Vue Desktop Template
The most versatile project template to create Gist Desktop Apps is the Vue Desktop Template which is capable of building both a standard vue-lite .NET Core Web App that can optionally be bundled into a single GitHub Gist and run as a Gist Desktop App:
Running Gist Desktop Apps
If you haven't already, install the app dotnet tool which is the only app required to run all Gist and Sharp Desktop Apps:
$ dotnet tool install -g app
OSX or Linux users can run Sharp Apps with the cross-platform x dotnet tool instead
Now everyone can launch a Windows Desktop Sharp App by specifying the name of the App they want to open with:
$ app open redis
redis
Instant Run Without Installation
This searches the global App registry for the link to the App, and in this case launches the Redis Sharp App within a Chromium Desktop shell as seen above.
The list of available apps is also visible from the command-line with:
$ app open
1. redis Simple, lightweight, versatile Redis Admin UI by @ServiceStack
2. spirals Explore and generate different Spirals with SVG by @ServiceStack
3. blog Minimal, multi-user Twitter Auth blogging app by @ServiceStack
4. rockwind Example combining Rockstars website + data-driven Northwind Browser by @ServiceStack
5. redis-html Redis Admin Viewer developed as server-generated HTML Website by @sharp-apps
6. plugins Extend Apps with Plugins, ServiceStack Services and other C# extensions by @sharp-apps
7. chat Extensible App with custom AppHost using OAuth + SSE for real-time Chat by @sharp-apps
Run Apps From URLs
Publishing your App to the global registry makes it more accessible via a friendly name, but if you don't want your App shared publicly or want to test it before publishing, it can also be run directly from a Gist URL, Gist Id, GitHub Repo or Release .zip Archive, e.g:
$ app open https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8
$ app open 189cd72bfaf480526e4b34814c80f2c0
$ app open https://github.com/sharp-apps/redis
$ app open https://github.com/sharp-apps/plugins/archive/v7.zip
Local Sharp Apps can simply run app
in the Sharp Apps directory:
$ app
Cross Platform
If you're using macOS or Linux you can run all Sharp Apps using the cross-platform x dotnet tool where it will launch in your preferred Web Browser instead:
$ x open redis
Every
app
command is substitutable withx
to run it within your preferred browser in Windows, macOS or Linux
Always Up-To-Date
Another unique characteristic of Sharp Apps launched with open
is that they always run the latest version of the App, thereby avoiding the need to implement an Update feature or maintain patch release versions.
Run Apps Offline
When Apps are launched with open
, a folder is created in the Users .sharp-apps
directory, e.g:
$HOME\.sharp-apps\redis
This is the current directory that the App is run from and where any files created by Apps will be saved to and preserved across App runs.
For Gist Apps this is an empty folder as the Gist files are loaded into memory, however to support being able to run Apps offline it also serializes all Gist files (after fetching all truncated files) to JSON at:
$HOME\.sharp-apps\redis.gist
This is so after Apps are launched once with open
, they can then be run locally with:
$ app run redis
Which will load the Gist files from the serialized redis.gist JSON blob instead of downloading them from the gist on GitHub. This is useful for times you don't have an Internet connection, or GitHub is down. However as small Apps like redis start instantly,
it's preferred to run them with app open redis
so you're always running the latest version.
Run Local Modified Versions Of Existing Sharp Apps
Installing or running a Sharp App will install it in the $HOME\.sharp-apps\
folder but if you want to make changes to it, it's easier
to install it in a local directory. For Sharp Apps published in a GitHub Repo you can either clone the repo or use the dotnet tools to
install it from the command-line with the new
command to specify the Repo you want to download and the -source
parameter to specify
which GitHub User or Organization.
E.g. you can install https://github.com/sharp-apps/redis with:
$ app new redis -source sharp-apps
Where you can open it in a text editor like VS Code:
$ code redis
And run it in the VS Code terminal window, which during development you'll likely want to run it with your preferred Web Browser to access its Dev tools:
$ app
Then you'll be able to make changes to the App whilst it's running to see any changes in real-time.
Gist Apps
To modify Gist Apps you can use the mix dotnet tool to download a Gist files contents.
For example, you can download the redis Gist App with:
$ app mix https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8
Uninstall Apps
As Gist Apps are downloaded on-the-fly and loaded into memory there's not much to uninstall just an empty folder and a <app-name>.gist
JSON blob which you can either manually delete or get the dotnet tool to do it for you.
To view all App's you've opened, run:
$ app uninstall
This displays a list of Sharp App's you've run at least once:
Usage: app uninstall <app>
Installed Apps:
blog
chat
plugins
redis
rockwind
spirals
To delete all traces of the redis
App from your system, run:
$ app uninstall redis
Which removes the empty $HOME\.sharp-apps\redis
folder and $HOME\.sharp-apps\redis.gist
.
Gist Sharp Apps
If we peek into the markdown of app.md we can see the different ways Sharp Apps can be hosted, for redis we see that the entire App is published in a Gist:
- [redis](https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8)
Thanks to ServiceStacks' GistVirtualFiles support, Gist Apps were trivial to support which only required launching
the ServiceStack App with a configured GistVirtualFiles
that references the redis Gist, i.e:
appHost.InsertVirtualFileSources.Add(new GistVirtualFiles("6de7993333b457445793f51f6f520ea8"));
Hosting Apps in Gists provides numerous benefits: predominantly they're effectively a free, public, distributed app host which are tied to Authenticated GitHub Accounts and have a public version history of every commit so each change is visible.
GitHub also provides both a Web UI and HTTP UI to manage gists making them easy to modify, both manually and programmatically where you can use their Web UI to make a quick fix - which is instantly available the next time the app is launched.
We've already had quick look at the redis Gist App that provides a nice UI for querying and editing a Redis instance (an example of an App that can't be implemented as a website). Let's have a look at some other Gist Apps that are well suited for development as Sharp Apps:
Spirals
Open with:
$ app open spirals
Spirals in an example of a minimally useful App to explore and generate different spirals with SVG that showcases the productivity and
live Development experience of Sharp Apps where you can create an App from scratch with just a text editor and the x
tool, without
a single re-compile or app restart:
Publishing Gist Apps
Now that we've created an App, it's time to publish it to a Gist. To do this we need to create a personal access token with the gist permission so it's able to create gists.
You can provide your access token via the -token
command-line argument:
$ app publish -token {GITHUB_TOKEN}
Our recommendation is instead to set it in the GITHUB_TOKEN
Environment Variable to avoid needing to provide it each time.
Before publishing our App, our app.settings looks something like:
debug true
name Spirals
CefConfig { width:1100, height:900 }
To make your App listed in our Global App Directory, include the following metadata about your App:
appName <app alias> # required: alpha-numeric snake-case characters only, 30 chars max
description <app summary> # required: 20-150 chars
tags <app tags> # optional: space delimited, alpha-numeric snake-case, 3 tags max
The appName
is the globally unique short alias you want your App to be launched as, e.g:
app://my-alias
$ app open my-alias
If your app.settings contains the app metadata above, publishing the app will publish your App to a Gist & register your App's alias to the Global App Directory:
$ app publish
published to: https://gist.github.com/gistlyn/4e06df1f1b9099526a7c97721aa7f69c
Run published App:
x open spirals
When your App is published the first time, the created gist URL will be saved in a local .publish
text file & used for subsequent App publishes.
After it's published anyone will now be able to run your App locally with the global alias (if specified):
app://spirals
$ app open spirals
The Gist Id:
app://4e06df1f1b9099526a7c97721aa7f69c
$ app open 4e06df1f1b9099526a7c97721aa7f69c
Or Gist URL:
$ app open https://gist.github.com/gistlyn/4e06df1f1b9099526a7c97721aa7f69c
Users that are not on Windows can use the x
dotnet tool instead to launch your App in their preferred browser:
$ x open spirals
If preferred, Windows users can also launch your Gist Desktop App in their preferred browser (i.e. instead of a Chromium Desktop Shell) with the xapp://
URL Scheme:
xapp://spirals
Share your Windows Desktop App creations in minutes!
With its built-in publishing support, you can create an App from scratch, publish it to a gist, register it in the App directory - where your creations are ready for the world to use in minutes!
We are not aware of any other Desktop App solution that comes close to this level of turn around time.
Blog
Spirals are cool, but lets explore some more useful real-world Apps:
Open with:
$ app open blog
blog is an sqlite-powered multi-user blogging system, that in addition to supporting Markdown, also lets you use #Script
in your
posts so you're able to post "live executable documents" that can mix both content and executable scripts.
As we envisage sqlite to a popular storage option we'll go through a couple of ways to make use of it in Gist Apps as your Apps Gist files are read-only and loaded in memory where as SQLite is on disk.
To configure your App to use SQLite, add db sqlite and db.connection dbname.sqlite to:
app.settings
debug false
name Blog Web App
db sqlite
db.connection blog.sqlite
This will create an empty file at $HOME\.sharp-apps\blog\blog.sqlite
when your App is first launched.
You can then create your DB Schema and populate your database using Database Scripts
in a file called _init.html
which is executed just before your App is launched.
The blog App uses this approach for creating its' database and populating it with initial seed data if the database is empty, e.g:
_init.html
{{ `CREATE TABLE IF NOT EXISTS "Post"
(
"Id" INTEGER PRIMARY KEY AUTOINCREMENT,
"Slug" VARCHAR(8000) NULL,
"Title" VARCHAR(8000) NULL,
"Content" VARCHAR(8000) NULL,
"Created" VARCHAR(8000) NOT NULL,
"CreatedBy" VARCHAR(8000) NOT NULL,
"Modified" VARCHAR(8000) NOT NULL,
"ModifiedBy" VARCHAR(8000) NOT NULL
);
CREATE TABLE IF NOT EXISTS "UserInfo"
(
"UserName" VARCHAR(8000) PRIMARY KEY,
"DisplayName" VARCHAR(8000) NULL,
"AvatarUrl" VARCHAR(8000) NULL,
"AvatarUrlLarge" VARCHAR(8000) NULL,
"Created" VARCHAR(8000) NOT NULL,
"Modified" VARCHAR(8000) NOT NULL
);`
|> dbExec
}}
{{ dbScalar(`SELECT COUNT(*) FROM Post`) |> to => postsCount }}
{{#if postsCount == 0 }}
{{ `datetime(CURRENT_TIMESTAMP,'localtime')` |> to => sqlNow }}
{{ `ServiceStack` |> to => user }}
========================
Create ServiceStack User - Contains same info as if was @ServiceStack authenticated via Twitter
========================
{{ `INSERT INTO UserInfo (UserName, DisplayName, AvatarUrl, AvatarUrlLarge, Created, Modified)
VALUES (@user, @user, @avatarUrl, @avatarUrlLarge, ${sqlNow}, ${sqlNow})`
|> dbExec({
user: 'ServiceStack',
avatarUrl: 'https://pbs.twimg.com/profile_images/876249730078056448/JuTVEkWX_normal.jpg',
avatarUrlLarge: 'https://pbs.twimg.com/profile_images/876249730078056448/JuTVEkWX.jpg'
})
}}
...
{{/if}}
The next time the blog
App is run it uses the existing $HOME\.sharp-apps\blog\blog.sqlite
and skips populating the database above.
rockwind
Querying Embedded Northwind SQLite Database:
Open with:
$ app open rockwind
Rockwind is an example of a larger (60+ files) multi-versatile App of hybrid content, data driven App with a UI and Web API over northwind tables that also combines multiple different layouts in a single App.
Its data-driven Web UI and Web API requires the northwind database which is included in the gist as northwind.readonly.sqlite
then in the init script it saves a copy to northwind.sqlite if it doesn't already exist:
_init.html
vfsFileSystem('.') |> to => fs
#if !fs.fileExists('northwind.sqlite') || fs.file('northwind.sqlite').Length == 0
fs.writeFile('northwind.sqlite', file('northwind.readonly.sqlite'))
/if
rockwind contains a number of other hidden useful gems like how easy it is to create multi-linked query reports:
northwind\order-report_id.html
As well as a dynamic SQL Studio UI that re-queries as-you-type:
northwind\sql\index.html
GitHub Sharp Apps
Up to this point we've only seen running Sharp Apps from Gists, but they can also be run from traditional GitHub repos as we can see from the chat App which links to its repo:
- [chat](https://github.com/sharp-apps/chat)
The difference between Gist an GitHub Repo Apps is that GitHub repos need to be installed before they're run.
When you run a GitHub Repo App with open
, e.g:
$ app open chat
It downloads the repo archive (either the last released version or current master archive), extracts its contents to the Apps
$HOME\.sharp-apps\chat
folder then runs it as a traditional Web App where its files are maintained on disk.
As this process takes a little longer to start than Gist Apps you may prefer to use run
for subsequent App runs:
$ app run chat
Where it will run it from disk, whereas open
will nuke the existing install, re-download the archive and extract it again before launching.
For GitHub Repo apps, open
is equivalent to re-running install
and run
each time:
$ app install chat
$ app run chat
GitHub Sharp App Commands
Whilst they work differently, open
, install
, run
and uninstall
have the same behavior for both Gist and GitHub Repo Apps, i.e:
open
- Always run the latest version of the Appinstall
- Download the App only so it can be run offlinerun
- Run the locally downloaded Appuninstall
- Remove all traces of previously installed or opened Apps
chat
Open with:
$ app open chat
chat is an example of the ultimate form of extensibility where instead of just being able to add Services, Filters and Plugins, you can add your entire AppHost which Sharp Apps will use instead of its own. This vastly expands the use-cases that can be built with Sharp Apps as it gives you complete fine-grained control over exactly how your App is configured.
Plugins
The last packaging option supported for running Sharp Apps is being able to link to a specific GitHub Release version of your App, e.g:
- [plugins](https://github.com/sharp-apps/plugins/archive/v7.zip)
Which will ensure you're always running the same version of the App which is useful in being able to easily run and compare different App versions or be able to support beta releases of your App that you don't want everyone to use yet.
Open with:
$ app open plugins
plugins showcases the easy extensibility of Sharp Apps which allow "no touch" sharing of ServiceStack Plugins, Services, Script Methods, Sharp Code Pages and Validators contained within .dll's or .exe's dropped in a Sharp Apps /plugins folder which are auto-registered on startup. The source code for all plugins used in this App were built from the .NET 5.0 projects in the /plugins/src folder.