Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
adfcb19
Avoid accidental data trunctation when outputting JSON through Serilog.
Aug 26, 2026
64300d1
Tidy up test
Aug 26, 2026
6ac4531
Drop a comment
Aug 26, 2026
4b64c34
More test tidiying
Aug 26, 2026
0ef33d9
Merge pull request #493 from nblumhardt-ro/trace-depth
nblumhardt Aug 28, 2026
519f104
add arm64 support for Windows
KodrAus Aug 28, 2026
e0a8e92
Merge pull request #495 from KodrAus/feat/win-arm64
nblumhardt Aug 28, 2026
a0fb7e7
Migrate data handling from Serilog's `LogEvent` across to `System.Tex…
Sep 1, 2026
96b4019
Localize Serilog event conversion to the sample application - we don'…
Sep 1, 2026
d535017
Trim down more
Sep 1, 2026
33b115d
More tidy-up
Sep 1, 2026
8b828b3
More cleanup
Sep 1, 2026
7ebd2ad
More clean-up
Sep 1, 2026
5aadf57
No need to maintain compat with undocumented internal functions we pr…
Sep 1, 2026
8d9fad0
Separate Data/ from Syntax/ - still not particularly cohesive, but sh…
Sep 1, 2026
45551a2
Drop some brittle test scaffolding
Sep 1, 2026
575be72
Fix namespace
Sep 1, 2026
8d598c6
Remove half comment
Sep 1, 2026
725b977
Namespaces, using
Sep 1, 2026
c230f57
More tidy-up
Sep 2, 2026
edf8505
Add coverage for `search --filter`.
Sep 2, 2026
2675a1d
Feedback
Sep 2, 2026
8730efe
Merge pull request #496 from nblumhardt-ro/seq-syntax-v2
KodrAus Sep 2, 2026
479c2a1
Replicate the `trace` command's `--column` feature for `search`.
Aug 21, 2026
c1ec28b
Include signal columns in search command output
Aug 21, 2026
f1bcdf1
Signal columns tests.
Aug 21, 2026
142f019
Add `--column` support to `seqcli tail`.
Aug 25, 2026
bb7627a
Add npm package and publish.
liammclennan-ro Sep 3, 2026
f987446
Include updated Seq.Syntax
nblumhardt Sep 3, 2026
2388cc1
Rework now that Seq.Syntax can evaluate column expressions directly i…
Sep 3, 2026
f96c733
Add a build README
liammclennan-ro Sep 4, 2026
35a1580
Use the regular README as the npm README
liammclennan-ro Sep 7, 2026
99b595d
Merge pull request #497 from nblumhardt-ro/columns-in-search
nblumhardt Sep 7, 2026
c7116c1
Merge pull request #498 from liammclennan-ro/npm-publishing
KodrAus Sep 7, 2026
f0386a0
Extend npm publish timeout
liammclennan-ro Sep 7, 2026
629c95d
Merge pull request #499 from liammclennan-ro/extend-npm-publishing-ti…
KodrAus Sep 8, 2026
22527ed
Tidy exception reporting up a bit; log full exception details at oute…
nblumhardt Sep 8, 2026
17a8835
Test case
nblumhardt Sep 8, 2026
53d6757
Merge pull request #500 from nblumhardt/tidy-exception-reporting
KodrAus Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ jobs:
shell: pwsh
run: |
./build/Build.Windows.ps1
- name: Upload archives
# Consumed by the publish-npm job
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v7
with:
name: archives
path: |
artifacts/seqcli-*-*.zip
artifacts/seqcli-*-*.tar.gz
if-no-files-found: error
retention-days: 1

build-linux:
name: Build (Linux)
Expand Down Expand Up @@ -69,3 +80,45 @@ jobs:
shell: pwsh
run: |
./build/Build.Linux.ps1 -SeqDockerTag $env:SEQ_DOCKER_TAG

publish-npm:
name: Publish (npm)
runs-on: ubuntu-24.04
needs: build-windows

# Mirrors NuGet publishing: builds from any branch this workflow targets (dev builds as
# prereleases under the `dev` dist-tag, main builds as `latest`), but never pull requests.
if: github.event_name != 'pull_request'

permissions:
contents: read
# Required for npm trusted publishing (OIDC) and provenance
id-token: write

steps:
- uses: actions/checkout@v6
- name: Setup
uses: actions/setup-node@v7
with:
node-version: 24.x
# Bootstrap only: together with NODE_AUTH_TOKEN below, authenticates using the NPM_TOKEN
# secret. Once trusted publishing is configured for every @datalust/seqcli* package
# (bound to this workflow file, ci.yml), remove `registry-url` here and `NODE_AUTH_TOKEN`
# below so that npm authenticates with the OIDC token instead.
registry-url: https://registry.npmjs.org/
- name: Update npm
# Trusted publishing and automatic provenance require npm 11.5.1 or later
run: |
npm install -g npm@latest
npm --version
- name: Download archives
uses: actions/download-artifact@v8
with:
name: archives
path: npm-archives
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
shell: pwsh
run: |
./build/Build.Npm.ps1 -ArchiveDir ./npm-archives
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ x64/
x86/
bld/
[Bb]in/
# The npm launcher package keeps its script in bin/
!npm/seqcli/bin/
[Oo]bj/
[Ll]og/

Expand Down Expand Up @@ -296,3 +298,7 @@ global.json
.claude/
.qwen/
.agents/

# npm packaging staging area (build/Build.Npm.ps1)
npm-staging/
npm-archives/
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ The Seq installer for Windows includes `seqcli`. Otherwise, download the [releas
dotnet tool install --global seqcli
```

With Node.js installed, `seqcli` can be installed from npm using:

```
npm install -g @datalust/seqcli
```

On Windows, if the Seq installation directory is on your `PATH`, the `seqcli` bundled with Seq may take precedence over the npm-installed copy; `where seqcli` shows the resolution order, and `npx @datalust/seqcli <command>` always runs the npm version.

To set a default server URL and API key, run:

```
Expand Down Expand Up @@ -1464,6 +1472,8 @@ seqcli search -f "@Exception like '%TimeoutException%'" -c 30
| ------ | ----------- |
| `-f`, `--filter=VALUE` | A filter to apply to the search, for example `Host = 'xmpweb-01.example.com'` |
| `-c`, `--count=VALUE` | The maximum number of events to retrieve; the default is 1 |
| `--column=VALUE` | A column to display preceding each event's message; any Seq expression can be supplied, for example `OrderId`, `@SpanKind`, or `@Resource['service.name']`; this argument can be used multiple times, adding columns in order; applies to plain-text output only |
| `--no-signal-columns` | Do not show columns associated with the specified signal expression |
| `--start=VALUE` | ISO 8601 date/time to query from |
| `--end=VALUE` | ISO 8601 date/time to query to |
| `--json` | Print output in newline-delimited JSON (the default is plain text) |
Expand Down Expand Up @@ -1636,6 +1646,8 @@ Stream log events matching a filter.
| Option | Description |
| ------ | ----------- |
| `-f`, `--filter=VALUE` | An optional server-side filter to apply to the stream, for example `@Level = 'Error'` |
| `--column=VALUE` | A column to display preceding each event's message; any Seq expression can be supplied, for example `OrderId`, `@SpanKind`, or `@Resource['service.name']`; this argument can be used multiple times, adding columns in order; applies to plain-text output only |
| `--no-signal-columns` | Do not show columns associated with the specified signal expression |
| `--json` | Print output in newline-delimited JSON (the default is plain text) |
| `--no-color` | Don't colorize text output |
| `--force-color` | Force redirected output to have ANSI color (unless `--no-color` is also specified) |
Expand Down
9 changes: 9 additions & 0 deletions build/Build.Common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ function Get-SemVer()
$base + "." + $revision
}
}

function Get-NpmVersion($version)
{
# npm requires strict semver, which forbids leading zeros in numeric identifiers; the build number
# is zero-padded (e.g. 2026.1.02616), so strip the padding from the patch component (-> 2026.1.2616).
# Prerelease suffixes are alphanumeric identifiers and are left as-is.
if ($version -notmatch '^(\d+)\.(\d+)\.(\d+)(.*)$') { throw "Unrecognized version: $version" }
"$([int]$Matches[1]).$([int]$Matches[2]).$([int]$Matches[3])$($Matches[4])"
}
267 changes: 267 additions & 0 deletions build/Build.Npm.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# Publishes the npm packages for a seqcli build: one `@datalust/seqcli-<rid>` package per release
# archive, then the launcher package `@datalust/seqcli` (from ./npm/seqcli) with its
# optionalDependencies pinned to the same version.
#
# In CI (see publish-npm in .github/workflows/ci.yml) the archives come from the build-windows job's
# artifacts, so dev builds are published as prereleases (dist-tag `dev`) and main builds as `latest`,
# matching NuGet. Packages that already exist on the registry at the target version are skipped, so
# a partially-failed run can simply be re-run.
#
# Usage:
# ./build/Build.Npm.ps1 -ArchiveDir ./npm-archives # CI: version from Get-SemVer
# ./build/Build.Npm.ps1 -Version 2026.1.02616 # (re)publish GitHub release v2026.1.02616
# ./build/Build.Npm.ps1 -Version 2026.1.02616 -ArchiveDir ./x -DryRun # stage and `npm pack` only
param(
# Build version as it appears in archive names, e.g. 2026.1.02616 or 2026.1.02700-dev-02700.
# Defaults to Get-SemVer, which in CI reproduces the version computed by the build jobs.
[string] $Version,

# npm dist-tag; defaults to `latest` for release versions and `dev` for prereleases.
[string] $DistTag,

# Directory containing seqcli-<version>-<rid>.zip|.tar.gz archives; when omitted, the archives
# are downloaded from GitHub release v<version> with `gh release download`.
[string] $ArchiveDir,

# GitHub repository to download release assets from. Also identifies the repository whose CI
# publishes via npm trusted publishing (forks without an NPM_TOKEN skip publishing).
[string] $Repo = 'datalust/seqcli',

# Stage the packages and run `npm pack` instead of `npm publish`.
[switch] $DryRun
)

Push-Location $PSScriptRoot/../

. ./build/Build.Common.ps1

$ErrorActionPreference = 'Stop'

$scope = '@datalust'
$launcherName = "$scope/seqcli"
$staging = './npm-staging'

if (-not $Version) {
$Version = Get-SemVer
}

$npmVersion = Get-NpmVersion $Version

if (-not $DistTag) {
$DistTag = @{ $true = 'dev'; $false = 'latest' }[$npmVersion.Contains('-')]
}

Write-Host "Release version: $Version"
Write-Host "npm version: $npmVersion"
Write-Host "npm dist-tag: $DistTag"
Write-Host "Dry run: $DryRun"

if (-not $DryRun -and -not $env:NODE_AUTH_TOKEN -and $env:GITHUB_REPOSITORY -ne $Repo) {
# Forks have neither the NPM_TOKEN secret nor a trusted publisher configuration.
Write-Host "Skipping npm publishing: no npm credentials are available in this environment"
Pop-Location
exit 0
}

function Get-Rids
{
$([xml](Get-Content ./src/SeqCli/SeqCli.csproj)).Project.PropertyGroup.RuntimeIdentifiers.Split(';')
}

function Get-PlatformSpec($rid)
{
$os = switch -Wildcard ($rid) {
'win-*' { 'win32' }
'osx-*' { 'darwin' }
'linux-*' { 'linux' }
default { throw "Unrecognized RID: $rid" }
}

$cpu = ($rid -split '-')[-1]

$libc = $null
if ($rid -like 'linux-musl-*') { $libc = 'musl' }
elseif ($rid -like 'linux-*') { $libc = 'glibc' }

return @{ os = $os; cpu = $cpu; libc = $libc; isWindows = ($os -eq 'win32') }
}

function Get-ReleaseArchive($rid)
{
$pattern = "seqcli-$Version-$rid.*"

if ($ArchiveDir) {
$archive = Get-ChildItem -Path $ArchiveDir -Filter $pattern | Select-Object -First 1
if (-not $archive) { throw "No archive matching $pattern in $ArchiveDir" }
return $archive.FullName
}

$downloads = "$staging/download"
New-Item -ItemType Directory -Force -Path $downloads | Out-Null

& gh release download "v$Version" --repo $Repo --dir $downloads --pattern $pattern --clobber
if ($LASTEXITCODE -ne 0) { throw "Downloading $pattern from release v$Version failed" }

$archive = Get-ChildItem -Path $downloads -Filter $pattern | Select-Object -First 1
if (-not $archive) { throw "Release v$Version has no asset matching $pattern" }
return $archive.FullName
}

function Expand-ReleaseArchive($archive, $destination)
{
if (Test-Path $destination) { Remove-Item -Recurse -Force $destination }
New-Item -ItemType Directory -Force -Path $destination | Out-Null

if ($archive -like '*.zip') {
Expand-Archive -Path $archive -DestinationPath $destination -Force
} else {
& tar -xzf $archive -C $destination
if ($LASTEXITCODE -ne 0) { throw "Extracting $archive failed" }
}

# The archives contain a single `seqcli-<version>-<rid>/` root folder; the package needs the
# binary at its root, so lift the contents up one level.
$entries = @(Get-ChildItem -Force $destination)
if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) {
$root = $entries[0].FullName
Get-ChildItem -Force $root | Move-Item -Destination $destination
Remove-Item -Force $root
}
}

function Write-PlatformPackageJson($rid, $spec, $destination)
{
$package = Get-Content ./npm/platform-package.json -Raw | ConvertFrom-Json -AsHashtable
$package.name = "$scope/seqcli-$rid"
$package.version = $npmVersion
$package.description = $package.description.Replace('{{rid}}', $rid)
$package.os = @($spec.os)
$package.cpu = @($spec.cpu)
if ($spec.libc) { $package.libc = @($spec.libc) }

$package | ConvertTo-Json -Depth 5 | Set-Content -Path "$destination/package.json" -NoNewline
}

function Test-NpmPublished($name)
{
$output = & npm view "$name@$npmVersion" version --json 2>$null
return ($LASTEXITCODE -eq 0) -and -not [string]::IsNullOrWhiteSpace(($output -join ''))
}

function Publish-NpmPackage($name, $directory)
{
if ($DryRun) {
Write-Host "Packing $name@$npmVersion"
$tarballs = "$staging/tarballs"
New-Item -ItemType Directory -Force -Path $tarballs | Out-Null
& npm pack $directory --pack-destination $tarballs
if ($LASTEXITCODE -ne 0) { throw "Packing $name failed" }
return
}

if (Test-NpmPublished $name) {
Write-Host "Skipping $name@$npmVersion; already published"
return
}

Write-Host "Publishing $name@$npmVersion with dist-tag $DistTag"
$arguments = @('publish', $directory, '--access', 'public', '--tag', $DistTag)
if ($env:GITHUB_ACTIONS -eq 'true') { $arguments += '--provenance' }
& npm @arguments
if ($LASTEXITCODE -ne 0) { throw "Publishing $name failed" }
}

function Assert-NpmPublished($name)
{
# `npm publish` returns as soon as the registry accepts the upload, but the read path (the
# packument served via npm's CDN) is updated asynchronously and can lag by several minutes,
# particularly for the first-ever publish of a package name. Poll for up to ten minutes.
$timeout = [TimeSpan]::FromMinutes(10)
$interval = 15
$started = Get-Date

while ($true) {
if (Test-NpmPublished $name) { return }

$elapsed = (Get-Date) - $started
if ($elapsed -ge $timeout) { break }

Write-Host ("Waiting for {0}@{1} to become visible on the registry ({2:mm\:ss} elapsed)" -f $name, $npmVersion, $elapsed)
Start-Sleep -Seconds $interval
}

throw "$name@$npmVersion is not visible on the registry after $($timeout.TotalMinutes) minutes"
}

function Stage-PlatformPackage($rid)
{
$spec = Get-PlatformSpec $rid
$directory = "$staging/seqcli-$rid"

$archive = Get-ReleaseArchive $rid
Write-Host "Staging $scope/seqcli-$rid from $archive"
Expand-ReleaseArchive $archive $directory

$binary = Join-Path $directory $(if ($spec.isWindows) { 'seqcli.exe' } else { 'seqcli' })
if (-not (Test-Path $binary)) { throw "Expected $binary in $archive" }

if (-not $spec.isWindows) {
& chmod +x $binary
if ($LASTEXITCODE -ne 0) { throw "chmod failed for $binary" }
}

Write-PlatformPackageJson $rid $spec $directory

return $directory
}

function Stage-LauncherPackage($rids)
{
$directory = "$staging/seqcli"
if (Test-Path $directory) { Remove-Item -Recurse -Force $directory }
Copy-Item -Recurse ./npm/seqcli $directory

# The launcher package is the one users see on npmjs.com, so it carries the repository README
# and license rather than maintaining separate copies under ./npm.
Copy-Item ./README.md "$directory/README.md"
Copy-Item ./LICENSE "$directory/LICENSE"

$package = Get-Content "$directory/package.json" -Raw | ConvertFrom-Json -AsHashtable
$package.version = $npmVersion
$package.optionalDependencies = [ordered]@{}
foreach ($rid in $rids) {
$package.optionalDependencies["$scope/seqcli-$rid"] = $npmVersion
}

$package | ConvertTo-Json -Depth 5 | Set-Content -Path "$directory/package.json" -NoNewline

return $directory
}

if (Test-Path $staging) { Remove-Item -Recurse -Force $staging }
New-Item -ItemType Directory -Force -Path $staging | Out-Null

$rids = Get-Rids

foreach ($rid in $rids) {
$directory = Stage-PlatformPackage $rid
Publish-NpmPackage "$scope/seqcli-$rid" $directory
}

if (-not $DryRun) {
# Never expose a launcher whose optional dependencies can't all be resolved.
foreach ($rid in $rids) {
Assert-NpmPublished "$scope/seqcli-$rid"
}
}

$launcherDirectory = Stage-LauncherPackage $rids
Publish-NpmPackage $launcherName $launcherDirectory

if (-not $DryRun) {
Assert-NpmPublished $launcherName
& npm view $launcherName dist-tags
Write-Host "Install with: npm install -g $launcherName@$npmVersion"
}

Pop-Location
Loading