Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ a CI job fails when they disagree.

## [Unreleased]

### Fixed

- **`fleet skill` failed on an installed 0.8.0.** The 0.8.0 archives carry
`skills/`, but `install.sh` and `install.ps1` copy a release's directories
by name and did not name it, so `fleet skill show` and `fleet skill install`
could not find `SKILL.md`. Both installers now copy it, and a test fails if
the bundle gains a directory the installers do not copy. The installers are
fetched from `main`, so re-running the install command fixes an existing
0.8.0 install; no new release is needed.

## [0.8.0] — 2026-10-15

The fleet as something a program or an AI agent can drive: a versioned control
Expand Down
45 changes: 45 additions & 0 deletions fleet/test/installers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* The installers copy a release's directories by name rather than replacing the
* install directory, because the install directory is also FLEET_HOME and holds
* the fleet's data. So a directory build.mjs adds to the bundle is dropped on
* install unless both installers name it too.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
import { FLEET } from "./helpers.js";

const read = (rel: string) => readFileSync(path.join(FLEET, rel), "utf8");

function bundleEntries(): string[] {
const src = read("build.mjs");
const names = new Set<string>();
for (const m of src.matchAll(/path\.join\(OUT, "([a-z-]+)/g)) names.add(m[1]);
for (const m of src.matchAll(/^\s*\["[^"]+", "([a-z-]+)"\],?$/gm)) names.add(m[1]);
return [...names].sort();
}

function shEntries(): string[] {
const m = /for entry in ([a-z -]+); do/.exec(read("../install.sh"));
assert.ok(m, "install.sh: the `for entry in ...` copy loop was not found");
return m[1].trim().split(/\s+/).sort();
}

function ps1Entries(): string[] {
const m = /foreach \(\$entry in @\(([^)]*)\)\)/.exec(read("../install.ps1"));
assert.ok(m, "install.ps1: the `foreach ($entry in @(...))` copy loop was not found");
return [...m[1].matchAll(/'([a-z-]+)'/g)].map((x) => x[1]).sort();
}

test("build.mjs's top-level entries are found", () => {
assert.ok(bundleEntries().includes("bin"), bundleEntries().join(" "));
});

test("install.sh copies every directory the bundle has", () => {
assert.deepEqual(shEntries(), bundleEntries());
});

test("install.ps1 copies every directory the bundle has", () => {
assert.deepEqual(ps1Entries(), bundleEntries());
});
2 changes: 1 addition & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ function Install-Fleet {
# recorded -- and an upgrade that deletes your history is a worse
# outcome than an upgrade that fails.
New-Item -ItemType Directory -Path $dir -Force | Out-Null
foreach ($entry in @('bin', 'runner-web', 'dash', 'examples', 'schemas')) {
foreach ($entry in @('bin', 'runner-web', 'dash', 'examples', 'schemas', 'skills')) {
$from = Join-Path $root $entry
if (-not (Test-Path -LiteralPath $from)) { continue }
$to = Join-Path $dir $entry
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ main() {
# with it -- every result the fleet has ever recorded -- and an upgrade that
# deletes your history is a worse outcome than an upgrade that fails.
mkdir -p "$dir"
for entry in bin runner-web dash examples schemas; do
for entry in bin runner-web dash examples schemas skills; do
if [ -d "${root}/${entry}" ]; then
rm -rf "${dir:?}/${entry}"
cp -R "${root}/${entry}" "${dir}/${entry}"
Expand Down
Loading