@@ -78,8 +78,8 @@ WPF took goes back to the system. The price is a cold start of half a second or
|
||||
the next time the window is asked for.
|
||||
|
||||
`CursorLang.Core.dll` is what both hold in common: the models, the settings file,
|
||||
the layout tracking, the updates. No UI, and it must stay that way — whatever lands
|
||||
there lands in the background process.
|
||||
the layout tracking. No UI, and it must stay that way — whatever lands there lands
|
||||
in the background process.
|
||||
|
||||
The connection between the two is `settings.json` and nothing else. The window
|
||||
writes it — whole, into a temporary file moved into place in one step — and then
|
||||
@@ -162,57 +162,22 @@ after a switch of the mode, and that is a question asked once.
|
||||
|
||||
## Updates
|
||||
|
||||
The check runs when the settings window is opened, not when the machine is
|
||||
switched on: the background half no longer goes to the network at all, and there
|
||||
would be nothing in it to show the answer. The setting in the window says as much.
|
||||
The Store updates the app, and the app itself does nothing about it: there is no
|
||||
updates section in the window, no request to the network and no code for either.
|
||||
|
||||
The app looks for new versions among the releases of its own repository. A
|
||||
release counts when its tag is a plain version — `v1.2.3` or `1.2.3` — and an
|
||||
MSIX package is attached to it. A tag with anything else in it, `v1.2.3-beta`
|
||||
among them, is passed over: a pre-release version is asked for on purpose, not
|
||||
offered by the app.
|
||||
That is not a matter of taste but of what a signature costs. Windows installs an
|
||||
MSIX only when it trusts the signature on it, and a publicly trusted code signing
|
||||
certificate has turned out to be beyond reach — the certificate authorities that
|
||||
sell them will not issue one here, and those that would keep the key in a cloud
|
||||
HSM will not either, while a certificate on a USB token cannot be shipped in.
|
||||
Without a signature a package installs nowhere but a machine in developer mode,
|
||||
so there is nothing to hand out from a release and nothing for an updater to
|
||||
find. The Store signs the package with its own certificate and updates the app
|
||||
by itself, which leaves the whole question to it.
|
||||
|
||||
Out of the attached files the `.msixbundle` is preferred — it carries both
|
||||
architectures. Failing that, the package whose name holds the architecture of
|
||||
this machine is taken: `CursorLang-1.2.3.0-x64.msix`. Those are the names
|
||||
`build-msix.ps1` produces, so a release is made by attaching what it built.
|
||||
|
||||
The package is downloaded to the temp folder and handed to the Windows app
|
||||
installer: it shows the publisher, asks for a confirmation and replaces the
|
||||
installed version. Windows checks the signature, so the package attached to a
|
||||
release has to be signed — an unsigned one installs nowhere but a machine in
|
||||
developer mode; the pipeline signs what it attaches. The running app keeps
|
||||
working off the old files until it is restarted.
|
||||
|
||||
An app installed from the Store has no updates section at all: the Store
|
||||
updates it, and a package from the side is something Windows would not accept
|
||||
over it anyway.
|
||||
|
||||
The app asks about releases every time the settings window appears, and the
|
||||
answer stays in the section as it is — an unreachable network included. Opening
|
||||
that window is a deliberate act, rare enough for a request to cost nothing, so
|
||||
nothing is remembered between openings and there is nothing to turn off. The
|
||||
button next to the version asks the same question again on demand.
|
||||
|
||||
Where the releases are looked for is set in `UpdateOptions` — the repository
|
||||
belongs to whoever publishes the app, not to the user, so the values live in the
|
||||
build rather than in `settings.json`:
|
||||
|
||||
```csharp
|
||||
services.AddSingleton(new UpdateOptions
|
||||
{
|
||||
ServiceUri = new Uri("https://git.alrakis.kz/"), // the Gitea server itself
|
||||
Project = "alrakis/cursor-lang",
|
||||
});
|
||||
```
|
||||
|
||||
The releases come from Gitea, and its API sits on the server itself: the address
|
||||
is the one the repository is opened at in a browser, and the app asks
|
||||
`/api/v1/repos/{owner}/{repo}/releases` under it.
|
||||
|
||||
A closed repository needs a token. It is read from the `CURSORLANG_UPDATE_TOKEN`
|
||||
environment variable rather than kept in the source: a secret built into the app
|
||||
is a secret handed to everyone who got the app.
|
||||
The version of the running app is in the title of the settings window:
|
||||
`CursorLang 1.2.3 — Settings`. It is nowhere else in the interface, and it is
|
||||
there so that a bug report can name it.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -257,11 +222,10 @@ running agent and fails if any part of the WPF renderer is in it.
|
||||
|
||||
The pipelines live in `.gitea/workflows` and run on Gitea Actions. A pull
|
||||
request into `master` is built and tested; a tag of the form `v1.2.3` is built,
|
||||
tested, packed into an MSIX and published as a release with the signed packages
|
||||
attached. The version is taken from the tag alone — a tag shaped any other way
|
||||
stops the run right at the start. The package version ends up as `1.2.3.0`: the
|
||||
Store takes four numbers and keeps the last one for itself, so the tag has no
|
||||
say in it.
|
||||
tested and packed into an MSIX, which is left in the artifacts of the run. The
|
||||
version is taken from the tag alone — a tag shaped any other way stops the run
|
||||
right at the start. The package version ends up as `1.2.3.0`: the Store takes
|
||||
four numbers and keeps the last one for itself, so the tag has no say in it.
|
||||
|
||||
Both pipelines ask for a Windows runner labelled `windows-x64` with the .NET 10
|
||||
SDK and git-lfs on it. The checkout pulls LFS files: without them the icon is a
|
||||
@@ -271,31 +235,18 @@ need a desktop of their own — the end-to-end ones, and those that ask for the
|
||||
foreground window or the caret — skip themselves on a runner that lives as a
|
||||
service in session 0, where there is no desktop to show a window on.
|
||||
|
||||
The package is built twice over, because the Store and a release want different
|
||||
things of it.
|
||||
The package goes to the Store and nowhere else: it is unsigned, and Partner
|
||||
Center puts its own signature on it. So the run leaves it in the artifacts under
|
||||
the name `msix-1.2.3.0`, where whoever uploads it picks it up by hand; nothing
|
||||
is attached to the release, which Gitea makes for the tag by itself and which
|
||||
carries the tag alone. An unsigned package hanging off a release would look like
|
||||
something to install and install nowhere — see the section on updates.
|
||||
|
||||
The Store one carries the identity reserved in Partner Center and no signature —
|
||||
Partner Center signs it there. It never leaves the artifacts of the run: someone
|
||||
picks it up and uploads it by hand. Its name says which one it is —
|
||||
`CursorLang-1.2.3.0-store.msixbundle`. The identity comes from repository
|
||||
variables and falls back to the defaults of the script when unset:
|
||||
`MSIX_IDENTITY_NAME`, `MSIX_PUBLISHER` and `MSIX_PUBLISHER_DISPLAY_NAME`.
|
||||
|
||||
The one attached to the release is signed by the pipeline with a certificate
|
||||
from SSL.com, whose private key stays in their HSM and never comes down to the
|
||||
runner: eSigner CKA is installed on it as a key storage provider, and `signtool`
|
||||
asks that for the key the way it would ask a token. The account is three
|
||||
repository secrets — `ESIGNER_USERNAME`, `ESIGNER_PASSWORD` and
|
||||
`ESIGNER_TOTP_SECRET`; the last one is the secret eSigner hands out for
|
||||
automated signing, not a code read off a phone. They are checked before the
|
||||
build rather than at the signing step: a release without a signed package is
|
||||
not a release.
|
||||
|
||||
Its `Publisher` is not `MSIX_PUBLISHER` but the subject of that certificate,
|
||||
read off it in the run — Windows takes a package naming anyone else for a
|
||||
forgery. Together with the identity that subject decides the family name of the
|
||||
package, so both have to stay as they are from release to release, or an update
|
||||
installs beside the old version instead of replacing it.
|
||||
The identity comes from repository variables and falls back to the defaults of
|
||||
the script when unset: `MSIX_IDENTITY_NAME`, `MSIX_PUBLISHER` and
|
||||
`MSIX_PUBLISHER_DISPLAY_NAME`. Together the identity and the publisher decide
|
||||
the family name of the package, so both have to stay as they are from version to
|
||||
version, or the Store takes the next one for a different app.
|
||||
|
||||
## Building the MSIX package
|
||||
|
||||
@@ -320,14 +271,8 @@ The result is `artifacts\packages\CursorLang-<version>.msixbundle` covering x64
|
||||
and arm64; next to it lie the packages of single architectures. Upload the bundle
|
||||
to Partner Center as it is.
|
||||
|
||||
`-CertificateThumbprint` signs everything the build produces, with the
|
||||
certificate of that thumbprint out of the personal store of the current user;
|
||||
`-Publisher` then has to name its subject, and the script says so before it
|
||||
starts building rather than after. The pipeline is what normally signs, but the
|
||||
same works by hand once eSigner CKA — or a certificate of any other kind — has
|
||||
put a certificate into the store. `-PackageSuffix` goes at the end of the file
|
||||
names: the builds for the Store and for a release differ in what is inside them
|
||||
and in nothing else.
|
||||
Nothing here is signed: the Store signs the package itself, and for installing it
|
||||
on this machine the layout is registered instead — see `-Install` below.
|
||||
|
||||
The app ships with its own copy of .NET: Windows does not include .NET 10, and
|
||||
MSIX cannot install a runtime as a package dependency.
|
||||
|
||||
Reference in New Issue
Block a user