Merge branch 'next/major' of github.com:Start9Labs/start-os into bugfix/registry-info-short

This commit is contained in:
Aiden McClelland
2026-02-05 16:12:50 -07:00
282 changed files with 6803 additions and 5522 deletions

5
.claude/settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"attribution": {
"commit": ""
}
}

2
.gitignore vendored
View File

@@ -20,3 +20,5 @@ secrets.db
/compiled-*.tar
/build/lib/firmware
tmp
web/.i18n-checked
agents/USER.md

146
CLAUDE.md Normal file
View File

@@ -0,0 +1,146 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
StartOS is an open-source Linux distribution for running personal servers. It manages discovery, installation, network configuration, backups, and health monitoring of self-hosted services.
**Tech Stack:**
- Backend: Rust (async/Tokio, Axum web framework)
- Frontend: Angular 20 + TypeScript + TaigaUI
- Container runtime: Node.js/TypeScript with LXC
- Database/State: Patch-DB (git submodule) - storage layer with reactive frontend sync
- API: JSON-RPC via rpc-toolkit (see `agents/rpc-toolkit.md`)
- Auth: Password + session cookie, public/private key signatures, local authcookie (see `core/src/middleware/auth/`)
## Build & Development
See [CONTRIBUTING.md](CONTRIBUTING.md) for:
- Environment setup and requirements
- Build commands and make targets
- Testing and formatting commands
- Environment variables
**Quick reference:**
```bash
. ./devmode.sh # Enable dev mode
make update-startbox REMOTE=start9@<ip> # Fastest iteration (binary + UI)
make test-core # Run Rust tests
```
## Architecture
### Core (`/core`)
The Rust backend daemon. Main binaries:
- `startbox` - Main daemon (runs as `startd`)
- `start-cli` - CLI interface
- `start-container` - Runs inside LXC containers; communicates with host and manages subcontainers
- `registrybox` - Registry daemon
- `tunnelbox` - VPN/tunnel daemon
**Key modules:**
- `src/context/` - Context types (RpcContext, CliContext, InitContext, DiagnosticContext)
- `src/service/` - Service lifecycle management with actor pattern (`service_actor.rs`)
- `src/db/model/` - Patch-DB models (`public.rs` synced to frontend, `private.rs` backend-only)
- `src/net/` - Networking (DNS, ACME, WiFi, Tor via Arti, WireGuard)
- `src/s9pk/` - S9PK package format (merkle archive)
- `src/registry/` - Package registry management
**RPC Pattern:** See `agents/rpc-toolkit.md`
### Web (`/web`)
Angular projects sharing common code:
- `projects/ui/` - Main admin interface
- `projects/setup-wizard/` - Initial setup
- `projects/start-tunnel/` - VPN management UI
- `projects/shared/` - Common library (API clients, components)
- `projects/marketplace/` - Service discovery
**Development:**
```bash
cd web
npm ci
npm run start:ui # Dev server with mocks
npm run build:ui # Production build
npm run check # Type check all projects
```
### Container Runtime (`/container-runtime`)
Node.js runtime that manages service containers via RPC. See `RPCSpec.md` for protocol.
**Container Architecture:**
```
LXC Container (uniform base for all services)
└── systemd
└── container-runtime.service
└── Loads /usr/lib/startos/package/index.js (from s9pk javascript.squashfs)
└── Package JS launches subcontainers (from images in s9pk)
```
The container runtime communicates with the host via JSON-RPC over Unix socket. Package JavaScript must export functions conforming to the `ABI` type defined in `sdk/base/lib/types.ts`.
**`/media/startos/` directory (mounted by host into container):**
| Path | Description |
|------|-------------|
| `volumes/<name>/` | Package data volumes (id-mapped, persistent) |
| `assets/` | Read-only assets from s9pk `assets.squashfs` |
| `images/<name>/` | Container images (squashfs, used for subcontainers) |
| `images/<name>.env` | Environment variables for image |
| `images/<name>.json` | Image metadata |
| `backup/` | Backup mount point (mounted during backup operations) |
| `rpc/service.sock` | RPC socket (container runtime listens here) |
| `rpc/host.sock` | Host RPC socket (for effects callbacks to host) |
**S9PK Structure:** See `agents/s9pk-structure.md`
### SDK (`/sdk`)
TypeScript SDK for packaging services (`@start9labs/start-sdk`).
- `base/` - Core types, ABI definitions, effects interface (`@start9labs/start-sdk-base`)
- `package/` - Full SDK for package developers, re-exports base
### Patch-DB (`/patch-db`)
Git submodule providing diff-based state synchronization. Changes to `db/model/public.rs` automatically sync to the frontend.
**Key patterns:**
- `db.peek().await` - Get a read-only snapshot of the database state
- `db.mutate(|db| { ... }).await` - Apply mutations atomically, returns `MutateResult`
- `#[derive(HasModel)]` - Derive macro for types stored in the database, generates typed accessors
**Generated accessor types** (from `HasModel` derive):
- `as_field()` - Immutable reference: `&Model<T>`
- `as_field_mut()` - Mutable reference: `&mut Model<T>`
- `into_field()` - Owned value: `Model<T>`
**`Model<T>` APIs** (from `db/prelude.rs`):
- `.de()` - Deserialize to `T`
- `.ser(&value)` - Serialize from `T`
- `.mutate(|v| ...)` - Deserialize, mutate, reserialize
- For maps: `.keys()`, `.as_idx(&key)`, `.as_idx_mut(&key)`, `.insert()`, `.remove()`, `.contains_key()`
## Supplementary Documentation
The `agents/` directory contains detailed documentation for AI assistants:
- `TODO.md` - Pending tasks for AI agents (check this first, remove items when completed)
- `USER.md` - Current user identifier (gitignored, see below)
- `rpc-toolkit.md` - JSON-RPC patterns and handler configuration
- `core-rust-patterns.md` - Common utilities and patterns for Rust code in `/core` (guard pattern, mount guards, etc.)
- `s9pk-structure.md` - S9PK package format structure
- `i18n-patterns.md` - Internationalization key conventions and usage in `/core`
### Session Startup
On startup:
1. **Check for `agents/USER.md`** - If it doesn't exist, prompt the user for their name/identifier and create it. This file is gitignored since it varies per developer.
2. **Check `agents/TODO.md` for relevant tasks** - Show TODOs that either:
- Have no `@username` tag (relevant to everyone)
- Are tagged with the current user's identifier
Skip TODOs tagged with a different user.
3. **Ask "What would you like to do today?"** - Offer options for each relevant TODO item, plus "Something else" for other requests.

View File

@@ -11,123 +11,190 @@ This guide is for contributing to the StartOS. If you are interested in packagin
```bash
/
├── assets/
├── container-runtime/
├── core/
├── build/
├── debian/
├── web/
├── image-recipe/
├── patch-db
└── sdk/
├── assets/ # Screenshots for README
├── build/ # Auxiliary files and scripts for deployed images
├── container-runtime/ # Node.js program managing package containers
├── core/ # Rust backend: API, daemon (startd), CLI (start-cli)
├── debian/ # Debian package maintainer scripts
├── image-recipe/ # Scripts for building StartOS images
├── patch-db/ # (submodule) Diff-based data store for frontend sync
├── sdk/ # TypeScript SDK for building StartOS packages
└── web/ # Web UIs (Angular)
```
#### assets
screenshots for the StartOS README
#### container-runtime
A NodeJS program that dynamically loads maintainer scripts and communicates with the OS to manage packages
#### core
An API, daemon (startd), and CLI (start-cli) that together provide the core functionality of StartOS.
#### build
Auxiliary files and scripts to include in deployed StartOS images
#### debian
Maintainer scripts for the StartOS Debian package
#### web
Web UIs served under various conditions and used to interact with StartOS APIs.
#### image-recipe
Scripts for building StartOS images
#### patch-db (submodule)
A diff based data store used to synchronize data between the web interfaces and server.
#### sdk
A typescript sdk for building start-os packages
See component READMEs for details:
- [`core`](core/README.md)
- [`web`](web/README.md)
- [`build`](build/README.md)
- [`patch-db`](https://github.com/Start9Labs/patch-db)
## Environment Setup
#### Clone the StartOS repository
```sh
git clone https://github.com/Start9Labs/start-os.git --recurse-submodules
cd start-os
```
#### Continue to your project of interest for additional instructions:
### Development Mode
- [`core`](core/README.md)
- [`web-interfaces`](web-interfaces/README.md)
- [`build`](build/README.md)
- [`patch-db`](https://github.com/Start9Labs/patch-db)
For faster iteration during development:
```sh
. ./devmode.sh
```
This sets `ENVIRONMENT=dev` and `GIT_BRANCH_AS_HASH=1` to prevent rebuilds on every commit.
## Building
This project uses [GNU Make](https://www.gnu.org/software/make/) to build its components. To build any specific component, simply run `make <TARGET>` replacing `<TARGET>` with the name of the target you'd like to build
All builds can be performed on any operating system that can run Docker.
This project uses [GNU Make](https://www.gnu.org/software/make/) to build its components.
### Requirements
- [GNU Make](https://www.gnu.org/software/make/)
- [Docker](https://docs.docker.com/get-docker/)
- [Docker](https://docs.docker.com/get-docker/) or [Podman](https://podman.io/)
- [NodeJS v20.16.0](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
- [sed](https://www.gnu.org/software/sed/)
- [grep](https://www.gnu.org/software/grep/)
- [awk](https://www.gnu.org/software/gawk/)
- [Rust](https://rustup.rs/) (nightly for formatting)
- [sed](https://www.gnu.org/software/sed/), [grep](https://www.gnu.org/software/grep/), [awk](https://www.gnu.org/software/gawk/)
- [jq](https://jqlang.github.io/jq/)
- [gzip](https://www.gnu.org/software/gzip/)
- [brotli](https://github.com/google/brotli)
- [gzip](https://www.gnu.org/software/gzip/), [brotli](https://github.com/google/brotli)
### Environment variables
### Environment Variables
- `PLATFORM`: which platform you would like to build for. Must be one of `x86_64`, `x86_64-nonfree`, `aarch64`, `aarch64-nonfree`, `raspberrypi`
- NOTE: `nonfree` images are for including `nonfree` firmware packages in the built ISO
- `ENVIRONMENT`: a hyphen separated set of feature flags to enable
- `dev`: enables password ssh (INSECURE!) and does not compress frontends
- `unstable`: enables assertions that will cause errors on unexpected inconsistencies that are undesirable in production use either for performance or reliability reasons
- `docker`: use `docker` instead of `podman`
- `GIT_BRANCH_AS_HASH`: set to `1` to use the current git branch name as the git hash so that the project does not need to be rebuilt on each commit
| Variable | Description |
|----------|-------------|
| `PLATFORM` | Target platform: `x86_64`, `x86_64-nonfree`, `aarch64`, `aarch64-nonfree`, `riscv64`, `raspberrypi` |
| `ENVIRONMENT` | Hyphen-separated feature flags (see below) |
| `PROFILE` | Build profile: `release` (default) or `dev` |
| `GIT_BRANCH_AS_HASH` | Set to `1` to use git branch name as version hash (avoids rebuilds) |
### Useful Make Targets
**ENVIRONMENT flags:**
- `dev` - Enables password SSH before setup, skips frontend compression
- `unstable` - Enables assertions and debugging with performance penalty
- `console` - Enables tokio-console for async debugging
**Platform notes:**
- `-nonfree` variants include proprietary firmware and drivers
- `raspberrypi` includes non-free components by necessity
- Platform is remembered between builds if not specified
### Make Targets
#### Building
| Target | Description |
|--------|-------------|
| `iso` | Create full `.iso` image (not for raspberrypi) |
| `img` | Create full `.img` image (raspberrypi only) |
| `deb` | Build Debian package |
| `all` | Build all Rust binaries |
| `uis` | Build all web UIs |
| `ui` | Build main UI only |
| `ts-bindings` | Generate TypeScript bindings from Rust types |
#### Deploying to Device
For devices on the same network:
| Target | Description |
|--------|-------------|
| `update-startbox REMOTE=start9@<ip>` | Deploy binary + UI only (fastest) |
| `update-deb REMOTE=start9@<ip>` | Deploy full Debian package |
| `update REMOTE=start9@<ip>` | OTA-style update |
| `reflash REMOTE=start9@<ip>` | Reflash as if using live ISO |
| `update-overlay REMOTE=start9@<ip>` | Deploy to in-memory overlay (reverts on reboot) |
For devices on different networks (uses [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)):
| Target | Description |
|--------|-------------|
| `wormhole` | Send startbox binary |
| `wormhole-deb` | Send Debian package |
| `wormhole-squashfs` | Send squashfs image |
#### Other
| Target | Description |
|--------|-------------|
| `format` | Run code formatting (Rust nightly required) |
| `test` | Run all automated tests |
| `test-core` | Run Rust tests |
| `test-sdk` | Run SDK tests |
| `test-container-runtime` | Run container runtime tests |
| `clean` | Delete all compiled artifacts |
## Testing
```bash
make test # All tests
make test-core # Rust tests (via ./core/run-tests.sh)
make test-sdk # SDK tests
make test-container-runtime # Container runtime tests
# Run specific Rust test
cd core && cargo test <test_name> --features=test
```
## Code Formatting
```bash
# Rust (requires nightly)
make format
# TypeScript/HTML/SCSS (web)
cd web && npm run format
```
## Code Style Guidelines
### Formatting
Run the formatters before committing. Configuration is handled by `rustfmt.toml` (Rust) and prettier configs (TypeScript).
### Documentation & Comments
**Rust:**
- Add doc comments (`///`) to public APIs, structs, and non-obvious functions
- Use `//` comments sparingly for complex logic that isn't self-evident
- Prefer self-documenting code (clear naming, small functions) over comments
**TypeScript:**
- Document exported functions and complex types with JSDoc
- Keep comments focused on "why" rather than "what"
**General:**
- Don't add comments that just restate the code
- Update or remove comments when code changes
- TODOs should include context: `// TODO(username): reason`
### Commit Messages
Use [Conventional Commits](https://www.conventionalcommits.org/):
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
**Types:**
- `feat` - New feature
- `fix` - Bug fix
- `docs` - Documentation only
- `style` - Formatting, no code change
- `refactor` - Code change that neither fixes a bug nor adds a feature
- `test` - Adding or updating tests
- `chore` - Build process, dependencies, etc.
**Examples:**
```
feat(web): add dark mode toggle
fix(core): resolve race condition in service startup
docs: update CONTRIBUTING.md with style guidelines
refactor(sdk): simplify package validation logic
```
- `iso`: Create a full `.iso` image
- Only possible from Debian
- Not available for `PLATFORM=raspberrypi`
- Additional Requirements:
- [debspawn](https://github.com/lkhq/debspawn)
- `img`: Create a full `.img` image
- Only possible from Debian
- Only available for `PLATFORM=raspberrypi`
- Additional Requirements:
- [debspawn](https://github.com/lkhq/debspawn)
- `format`: Run automatic code formatting for the project
- Additional Requirements:
- [rust](https://rustup.rs/)
- `test`: Run automated tests for the project
- Additional Requirements:
- [rust](https://rustup.rs/)
- `update`: Deploy the current working project to a device over ssh as if through an over-the-air update
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
- `reflash`: Deploy the current working project to a device over ssh as if using a live `iso` image to reflash it
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
- `update-overlay`: Deploy the current working project to a device over ssh to the in-memory overlay without restarting it
- WARNING: changes will be reverted after the device is rebooted
- WARNING: changes to `init` will not take effect as the device is already initialized
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
- `wormhole`: Deploy the `startbox` to a device using [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)
- When the build it complete will emit a command to paste into the shell of the device to upgrade it
- Additional Requirements:
- [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)
- `clean`: Delete all compiled artifacts

9
agents/TODO.md Normal file
View File

@@ -0,0 +1,9 @@
# AI Agent TODOs
Pending tasks for AI agents. Remove items when completed.
## Unreviewed CLAUDE.md Sections
- [ ] Architecture - Web (`/web`) - @MattDHill

View File

@@ -0,0 +1,249 @@
# Utilities & Patterns
This document covers common utilities and patterns used throughout the StartOS codebase.
## Util Module (`core/src/util/`)
The `util` module contains reusable utilities. Key submodules:
| Module | Purpose |
|--------|---------|
| `actor/` | Actor pattern implementation for concurrent state management |
| `collections/` | Custom collection types |
| `crypto.rs` | Cryptographic utilities (encryption, hashing) |
| `future.rs` | Future/async utilities |
| `io.rs` | File I/O helpers (create_file, canonicalize, etc.) |
| `iter.rs` | Iterator extensions |
| `net.rs` | Network utilities |
| `rpc.rs` | RPC helpers |
| `rpc_client.rs` | RPC client utilities |
| `serde.rs` | Serialization helpers (Base64, display/fromstr, etc.) |
| `sync.rs` | Synchronization primitives (SyncMutex, etc.) |
## Command Invocation (`Invoke` trait)
The `Invoke` trait provides a clean way to run external commands with error handling:
```rust
use crate::util::Invoke;
// Simple invocation
tokio::process::Command::new("ls")
.arg("-la")
.invoke(ErrorKind::Filesystem)
.await?;
// With timeout
tokio::process::Command::new("slow-command")
.timeout(Some(Duration::from_secs(30)))
.invoke(ErrorKind::Timeout)
.await?;
// With input
let mut input = Cursor::new(b"input data");
tokio::process::Command::new("cat")
.input(Some(&mut input))
.invoke(ErrorKind::Filesystem)
.await?;
// Piped commands
tokio::process::Command::new("cat")
.arg("file.txt")
.pipe(&mut tokio::process::Command::new("grep").arg("pattern"))
.invoke(ErrorKind::Filesystem)
.await?;
```
## Guard Pattern
Guards ensure cleanup happens when they go out of scope.
### `GeneralGuard` / `GeneralBoxedGuard`
For arbitrary cleanup actions:
```rust
use crate::util::GeneralGuard;
let guard = GeneralGuard::new(|| {
println!("Cleanup runs on drop");
});
// Do work...
// Explicit drop with action
guard.drop();
// Or skip the action
// guard.drop_without_action();
```
### `FileLock`
File-based locking with automatic unlock:
```rust
use crate::util::FileLock;
let lock = FileLock::new("/path/to/lockfile", true).await?; // blocking=true
// Lock held until dropped or explicitly unlocked
lock.unlock().await?;
```
## Mount Guard Pattern (`core/src/disk/mount/guard.rs`)
RAII guards for filesystem mounts. Ensures filesystems are unmounted when guards are dropped.
### `MountGuard`
Basic mount guard:
```rust
use crate::disk::mount::guard::MountGuard;
use crate::disk::mount::filesystem::{MountType, ReadOnly};
let guard = MountGuard::mount(&filesystem, "/mnt/target", ReadOnly).await?;
// Use the mounted filesystem at guard.path()
do_something(guard.path()).await?;
// Explicit unmount (or auto-unmounts on drop)
guard.unmount(false).await?; // false = don't delete mountpoint
```
### `TmpMountGuard`
Reference-counted temporary mount (mounts to `/media/startos/tmp/`):
```rust
use crate::disk::mount::guard::TmpMountGuard;
use crate::disk::mount::filesystem::ReadOnly;
// Multiple clones share the same mount
let guard1 = TmpMountGuard::mount(&filesystem, ReadOnly).await?;
let guard2 = guard1.clone();
// Mount stays alive while any guard exists
// Auto-unmounts when last guard is dropped
```
### `GenericMountGuard` trait
All mount guards implement this trait:
```rust
pub trait GenericMountGuard: std::fmt::Debug + Send + Sync + 'static {
fn path(&self) -> &Path;
fn unmount(self) -> impl Future<Output = Result<(), Error>> + Send;
}
```
### `SubPath`
Wraps a mount guard to point to a subdirectory:
```rust
use crate::disk::mount::guard::SubPath;
let mount = TmpMountGuard::mount(&filesystem, ReadOnly).await?;
let subdir = SubPath::new(mount, "data/subdir");
// subdir.path() returns the full path including subdirectory
```
## FileSystem Implementations (`core/src/disk/mount/filesystem/`)
Various filesystem types that can be mounted:
| Type | Description |
|------|-------------|
| `bind.rs` | Bind mounts |
| `block_dev.rs` | Block device mounts |
| `cifs.rs` | CIFS/SMB network shares |
| `ecryptfs.rs` | Encrypted filesystem |
| `efivarfs.rs` | EFI variables |
| `httpdirfs.rs` | HTTP directory as filesystem |
| `idmapped.rs` | ID-mapped mounts |
| `label.rs` | Mount by label |
| `loop_dev.rs` | Loop device mounts |
| `overlayfs.rs` | Overlay filesystem |
## Other Useful Utilities
### `Apply` / `ApplyRef` traits
Fluent method chaining:
```rust
use crate::util::Apply;
let result = some_value
.apply(|v| transform(v))
.apply(|v| another_transform(v));
```
### `Container<T>`
Async-safe optional container:
```rust
use crate::util::Container;
let container = Container::new(None);
container.set(value).await;
let taken = container.take().await;
```
### `HashWriter<H, W>`
Write data while computing hash:
```rust
use crate::util::HashWriter;
use sha2::Sha256;
let writer = HashWriter::new(Sha256::new(), file);
// Write data...
let (hasher, file) = writer.finish();
let hash = hasher.finalize();
```
### `Never` type
Uninhabited type for impossible cases:
```rust
use crate::util::Never;
fn impossible() -> Never {
// This function can never return
}
let never: Never = impossible();
never.absurd::<String>() // Can convert to any type
```
### `MaybeOwned<'a, T>`
Either borrowed or owned data:
```rust
use crate::util::MaybeOwned;
fn accept_either(data: MaybeOwned<'_, String>) {
// Use &*data to access the value
}
accept_either(MaybeOwned::from(&existing_string));
accept_either(MaybeOwned::from(owned_string));
```
### `new_guid()`
Generate a random GUID:
```rust
use crate::util::new_guid;
let guid = new_guid(); // Returns InternedString
```

100
agents/i18n-patterns.md Normal file
View File

@@ -0,0 +1,100 @@
# i18n Patterns in `core/`
## Library & Setup
**Crate:** [`rust-i18n`](https://crates.io/crates/rust-i18n) v3.1.5 (`core/Cargo.toml`)
**Initialization** (`core/src/lib.rs:3`):
```rust
rust_i18n::i18n!("locales", fallback = ["en_US"]);
```
This macro scans `core/locales/` at compile time and embeds all translations as constants.
**Prelude re-export** (`core/src/prelude.rs:4`):
```rust
pub use rust_i18n::t;
```
Most modules import `t!` via the prelude.
## Translation File
**Location:** `core/locales/i18n.yaml`
**Format:** YAML v2 (~755 keys)
**Supported languages:** `en_US`, `de_DE`, `es_ES`, `fr_FR`, `pl_PL`
**Entry structure:**
```yaml
namespace.sub.key-name:
en_US: "English text with %{param}"
de_DE: "German text with %{param}"
# ...
```
## Using `t!()`
```rust
// Simple key
t!("error.unknown")
// With parameter interpolation (%{name} in YAML)
t!("bins.deprecated.renamed", old = old_name, new = new_name)
```
## Key Naming Conventions
Keys use **dot-separated hierarchical namespaces** with **kebab-case** for multi-word segments:
```
<module>.<submodule>.<descriptive-name>
```
Examples:
- `error.incorrect-password` — error kind label
- `bins.start-init.updating-firmware` — startup phase message
- `backup.bulk.complete-title` — backup notification title
- `help.arg.acme-contact` — CLI help text for an argument
- `context.diagnostic.starting-diagnostic-ui` — diagnostic context status
### Top-Level Namespaces
| Namespace | Purpose |
|-----------|---------|
| `error.*` | `ErrorKind` display strings (see `src/error.rs`) |
| `bins.*` | CLI binary messages (deprecated, start-init, startd, etc.) |
| `init.*` | Initialization phase labels |
| `setup.*` | First-run setup messages |
| `context.*` | Context startup messages (diagnostic, setup, CLI) |
| `service.*` | Service lifecycle messages |
| `backup.*` | Backup/restore operation messages |
| `registry.*` | Package registry messages |
| `net.*` | Network-related messages |
| `middleware.*` | Request middleware messages (auth, etc.) |
| `disk.*` | Disk operation messages |
| `lxc.*` | Container management messages |
| `system.*` | System monitoring/metrics messages |
| `notifications.*` | User-facing notification messages |
| `update.*` | OS update messages |
| `util.*` | Utility messages (TUI, RPC) |
| `ssh.*` | SSH operation messages |
| `shutdown.*` | Shutdown-related messages |
| `logs.*` | Log-related messages |
| `auth.*` | Authentication messages |
| `help.*` | CLI help text (`help.arg.<arg-name>`) |
| `about.*` | CLI command descriptions |
## Locale Selection
`core/src/bins/mod.rs:15-36``set_locale_from_env()`:
1. Reads `LANG` environment variable
2. Strips `.UTF-8` suffix
3. Exact-matches against available locales, falls back to language-prefix match (e.g. `en_GB` matches `en_US`)
## Adding New Keys
1. Add the key to `core/locales/i18n.yaml` with all 5 language translations
2. Use the `t!("your.key.name")` macro in Rust code
3. Follow existing namespace conventions — match the module path where the key is used
4. Use kebab-case for multi-word segments
5. Translations are validated at compile time

226
agents/rpc-toolkit.md Normal file
View File

@@ -0,0 +1,226 @@
# rpc-toolkit
StartOS uses [rpc-toolkit](https://github.com/Start9Labs/rpc-toolkit) for its JSON-RPC API. This document covers the patterns used in this codebase.
## Overview
The API is JSON-RPC (not REST). All endpoints are RPC methods organized in a hierarchical command structure.
## Handler Functions
There are four types of handler functions, chosen based on the function's characteristics:
### `from_fn_async` - Async handlers
For standard async functions. Most handlers use this.
```rust
pub async fn my_handler(ctx: RpcContext, params: MyParams) -> Result<MyResponse, Error> {
// Can use .await
}
from_fn_async(my_handler)
```
### `from_fn_async_local` - Non-thread-safe async handlers
For async functions that are not `Send` (cannot be safely moved between threads). Use when working with non-thread-safe types.
```rust
pub async fn cli_download(ctx: CliContext, params: Params) -> Result<(), Error> {
// Non-Send async operations
}
from_fn_async_local(cli_download)
```
### `from_fn_blocking` - Sync blocking handlers
For synchronous functions that perform blocking I/O or long computations.
```rust
pub fn query_dns(ctx: RpcContext, params: DnsParams) -> Result<DnsResponse, Error> {
// Blocking operations (file I/O, DNS lookup, etc.)
}
from_fn_blocking(query_dns)
```
### `from_fn` - Sync non-blocking handlers
For pure functions or quick synchronous operations with no I/O.
```rust
pub fn echo(ctx: RpcContext, params: EchoParams) -> Result<String, Error> {
Ok(params.message)
}
from_fn(echo)
```
## ParentHandler
Groups related RPC methods into a hierarchy:
```rust
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
pub fn my_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new()
.subcommand("list", from_fn_async(list_handler).with_call_remote::<CliContext>())
.subcommand("create", from_fn_async(create_handler).with_call_remote::<CliContext>())
}
```
## Handler Extensions
Chain methods to configure handler behavior.
**Ordering rules:**
1. `with_about()` must come AFTER other CLI modifiers (`no_display()`, `with_custom_display_fn()`, etc.)
2. `with_call_remote()` must be the LAST adapter in the chain
| Method | Purpose |
|--------|---------|
| `.with_metadata("key", Value)` | Attach metadata for middleware |
| `.no_cli()` | RPC-only, not available via CLI |
| `.no_display()` | No CLI output |
| `.with_display_serializable()` | Default JSON/YAML output for CLI |
| `.with_custom_display_fn(\|_, res\| ...)` | Custom CLI output formatting |
| `.with_about("about.description")` | Add help text (i18n key) - **after CLI modifiers** |
| `.with_call_remote::<CliContext>()` | Enable CLI to call remotely - **must be last** |
### Correct ordering example:
```rust
from_fn_async(my_handler)
.with_metadata("sync_db", Value::Bool(true)) // metadata early
.no_display() // CLI modifier
.with_about("about.my-handler") // after CLI modifiers
.with_call_remote::<CliContext>() // always last
```
## Metadata by Middleware
Metadata tags are processed by different middleware. Group them logically:
### Auth Middleware (`middleware/auth/mod.rs`)
| Metadata | Default | Description |
|----------|---------|-------------|
| `authenticated` | `true` | Whether endpoint requires authentication. Set to `false` for public endpoints. |
### Session Auth Middleware (`middleware/auth/session.rs`)
| Metadata | Default | Description |
|----------|---------|-------------|
| `login` | `false` | Special handling for login endpoints (rate limiting, cookie setting) |
| `get_session` | `false` | Inject session ID into params as `__Auth_session` |
### Signature Auth Middleware (`middleware/auth/signature.rs`)
| Metadata | Default | Description |
|----------|---------|-------------|
| `get_signer` | `false` | Inject signer public key into params as `__Auth_signer` |
### Registry Auth (extends Signature Auth)
| Metadata | Default | Description |
|----------|---------|-------------|
| `admin` | `false` | Require admin privileges (signer must be in admin list) |
| `get_device_info` | `false` | Inject device info header for hardware filtering |
### Database Middleware (`middleware/db.rs`)
| Metadata | Default | Description |
|----------|---------|-------------|
| `sync_db` | `false` | Sync database after mutation, add `X-Patch-Sequence` header |
## Context Types
Different contexts for different execution environments:
- `RpcContext` - Web/RPC requests with full service access
- `CliContext` - CLI operations, calls remote RPC
- `InitContext` - During system initialization
- `DiagnosticContext` - Diagnostic/recovery mode
- `RegistryContext` - Registry daemon context
- `EffectContext` - Service effects context (container-to-host calls)
## Parameter Structs
Parameters use derive macros for JSON-RPC, CLI parsing, and TypeScript generation:
```rust
#[derive(Deserialize, Serialize, Parser, TS)]
#[serde(rename_all = "camelCase")] // JSON-RPC uses camelCase
#[command(rename_all = "kebab-case")] // CLI uses kebab-case
#[ts(export)] // Generate TypeScript types
pub struct MyParams {
pub package_id: PackageId,
}
```
### Middleware Injection
Auth middleware can inject values into params using special field names:
```rust
#[derive(Deserialize, Serialize, Parser, TS)]
pub struct MyParams {
#[ts(skip)]
#[serde(rename = "__Auth_session")] // Injected by session auth
session: InternedString,
#[ts(skip)]
#[serde(rename = "__Auth_signer")] // Injected by signature auth
signer: AnyVerifyingKey,
#[ts(skip)]
#[serde(rename = "__Auth_userAgent")] // Injected during login
user_agent: Option<String>,
}
```
## Common Patterns
### Adding a New RPC Endpoint
1. Define params struct with `Deserialize, Serialize, Parser, TS`
2. Choose handler type based on sync/async and thread-safety
3. Write handler function taking `(Context, Params) -> Result<Response, Error>`
4. Add to parent handler with appropriate extensions (display modifiers before `with_about`)
5. TypeScript types auto-generated via `make ts-bindings`
### Public (Unauthenticated) Endpoint
```rust
from_fn_async(get_info)
.with_metadata("authenticated", Value::Bool(false))
.with_display_serializable()
.with_about("about.get-info")
.with_call_remote::<CliContext>() // last
```
### Mutating Endpoint with DB Sync
```rust
from_fn_async(update_config)
.with_metadata("sync_db", Value::Bool(true))
.no_display()
.with_about("about.update-config")
.with_call_remote::<CliContext>() // last
```
### Session-Aware Endpoint
```rust
from_fn_async(logout)
.with_metadata("get_session", Value::Bool(true))
.no_display()
.with_about("about.logout")
.with_call_remote::<CliContext>() // last
```
## File Locations
- Handler definitions: Throughout `core/src/` modules
- Main API tree: `core/src/lib.rs` (`main_api()`, `server()`, `package()`)
- Auth middleware: `core/src/middleware/auth/`
- DB middleware: `core/src/middleware/db.rs`
- Context types: `core/src/context/`

122
agents/s9pk-structure.md Normal file
View File

@@ -0,0 +1,122 @@
# S9PK Package Format
S9PK is the package format for StartOS services. Version 2 uses a merkle archive structure for efficient downloading and cryptographic verification.
## File Format
S9PK files begin with a 3-byte header: `0x3b 0x3b 0x02` (magic bytes + version 2).
The archive is cryptographically signed using Ed25519 with prehashed content (SHA-512 over blake3 merkle root hash).
## Archive Structure
```
/
├── manifest.json # Package metadata (required)
├── icon.<ext> # Package icon - any image/* format (required)
├── LICENSE.md # License text (required)
├── dependencies/ # Dependency metadata (optional)
│ └── <package-id>/
│ ├── metadata.json # DependencyMetadata
│ └── icon.<ext> # Dependency icon
├── javascript.squashfs # Package JavaScript code (required)
├── assets.squashfs # Static assets (optional, legacy: assets/ directory)
└── images/ # Container images by architecture
└── <arch>/ # e.g., x86_64, aarch64, riscv64
├── <image-id>.squashfs # Container filesystem
├── <image-id>.json # Image metadata
└── <image-id>.env # Environment variables
```
## Components
### manifest.json
The package manifest contains all metadata:
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Package identifier (e.g., `bitcoind`) |
| `title` | string | Display name |
| `version` | string | Extended version string |
| `satisfies` | string[] | Version ranges this version satisfies |
| `releaseNotes` | string/object | Release notes (localized) |
| `canMigrateTo` | string | Version range for forward migration |
| `canMigrateFrom` | string | Version range for backward migration |
| `license` | string | License type |
| `wrapperRepo` | string | StartOS wrapper repository URL |
| `upstreamRepo` | string | Upstream project URL |
| `supportSite` | string | Support site URL |
| `marketingSite` | string | Marketing site URL |
| `donationUrl` | string? | Optional donation URL |
| `docsUrl` | string? | Optional documentation URL |
| `description` | object | Short and long descriptions (localized) |
| `images` | object | Image configurations by image ID |
| `volumes` | string[] | Volume IDs for persistent data |
| `alerts` | object | User alerts for lifecycle events |
| `dependencies` | object | Package dependencies |
| `hardwareRequirements` | object | Hardware requirements (arch, RAM, devices) |
| `hardwareAcceleration` | boolean | Whether package uses hardware acceleration |
| `gitHash` | string? | Git commit hash |
| `osVersion` | string | Minimum StartOS version |
| `sdkVersion` | string? | SDK version used to build |
### javascript.squashfs
Contains the package JavaScript that implements the `ABI` interface from `@start9labs/start-sdk-base`. This code runs in the container runtime and manages the package lifecycle.
The squashfs is mounted at `/usr/lib/startos/package/` and the runtime loads `index.js`.
### images/
Container images organized by architecture:
- **`<image-id>.squashfs`** - Container root filesystem
- **`<image-id>.json`** - Image metadata (entrypoint, user, workdir, etc.)
- **`<image-id>.env`** - Environment variables for the container
Images are built from Docker/Podman and converted to squashfs. The `ImageConfig` in manifest specifies:
- `arch` - Supported architectures
- `emulateMissingAs` - Fallback architecture for emulation
- `nvidiaContainer` - Whether to enable NVIDIA container support
### assets.squashfs
Static assets accessible to the package, mounted read-only at `/media/startos/assets/` in the container.
### dependencies/
Metadata for dependencies displayed in the UI:
- `metadata.json` - Just title for now
- `icon.<ext>` - Icon for the dependency
## Merkle Archive
The S9PK uses a merkle tree structure where each file and directory has a blake3 hash. This enables:
1. **Partial downloads** - Download and verify individual files
2. **Integrity verification** - Verify any subset of the archive
3. **Efficient updates** - Only download changed portions
4. **DOS protection** - Size limits enforced before downloading content
Files are sorted by priority for streaming (manifest first, then icon, license, dependencies, javascript, assets, images).
## Building S9PK
Use `start-cli s9pk pack` to build packages:
```bash
start-cli s9pk pack <manifest-path> -o <output.s9pk>
```
Images can be sourced from:
- Docker/Podman build (`--docker-build`)
- Existing Docker tag (`--docker-tag`)
- Pre-built squashfs files
## Related Code
- `core/src/s9pk/v2/mod.rs` - S9pk struct and serialization
- `core/src/s9pk/v2/manifest.rs` - Manifest types
- `core/src/s9pk/v2/pack.rs` - Packing logic
- `core/src/s9pk/merkle_archive/` - Merkle archive implementation

View File

@@ -111,6 +111,6 @@ if [ "$CHROOT_RES" -eq 0 ]; then
reboot
fi
umount -R /media/startos/next
umount /media/startos/next
umount /media/startos/upper
rm -rf /media/startos/upper /media/startos/next

View File

@@ -1,16 +1,21 @@
# Container RPC SERVER Specification
# Container RPC Server Specification
The container runtime exposes a JSON-RPC server over a Unix socket at `/media/startos/rpc/service.sock`.
## Methods
### init
initialize runtime (mount `/proc`, `/sys`, `/dev`, and `/run` to each image in `/media/images`)
Initialize the runtime and system.
called after os has mounted js and images to the container
#### params
#### args
`[]`
```ts
{
id: string,
kind: "install" | "update" | "restore" | null,
}
```
#### response
@@ -18,11 +23,16 @@ called after os has mounted js and images to the container
### exit
shutdown runtime
Shutdown runtime and optionally run exit hooks for a target version.
#### args
#### params
`[]`
```ts
{
id: string,
target: string | null, // ExtendedVersion or VersionRange
}
```
#### response
@@ -30,11 +40,11 @@ shutdown runtime
### start
run main method if not already running
Run main method if not already running.
#### args
#### params
`[]`
None
#### response
@@ -42,11 +52,11 @@ run main method if not already running
### stop
stop main method by sending SIGTERM to child processes, and SIGKILL after timeout
Stop main method by sending SIGTERM to child processes, and SIGKILL after timeout.
#### args
#### params
`{ timeout: millis }`
None
#### response
@@ -54,15 +64,16 @@ stop main method by sending SIGTERM to child processes, and SIGKILL after timeou
### execute
run a specific package procedure
Run a specific package procedure.
#### args
#### params
```ts
{
procedure: JsonPath,
id: string, // event ID
procedure: string, // JSON path (e.g., "/backup/create", "/actions/{name}/run")
input: any,
timeout: millis,
timeout: number | null,
}
```
@@ -72,18 +83,64 @@ run a specific package procedure
### sandbox
run a specific package procedure in sandbox mode
Run a specific package procedure in sandbox mode. Same interface as `execute`.
#### args
UNIMPLEMENTED: this feature is planned but does not exist
#### params
```ts
{
procedure: JsonPath,
id: string,
procedure: string,
input: any,
timeout: millis,
timeout: number | null,
}
```
#### response
`any`
### callback
Handle a callback from an effect.
#### params
```ts
{
id: number,
args: any[],
}
```
#### response
`null` (no response sent)
### eval
Evaluate a script in the runtime context. Used for debugging.
#### params
```ts
{
script: string,
}
```
#### response
`any`
## Procedures
The `execute` and `sandbox` methods route to procedures based on the `procedure` path:
| Procedure | Description |
|-----------|-------------|
| `/backup/create` | Create a backup |
| `/actions/{name}/getInput` | Get input spec for an action |
| `/actions/{name}/run` | Run an action with input |

View File

@@ -508,8 +508,10 @@ impl From<Error> for RpcError {
}
impl From<RpcError> for Error {
fn from(e: RpcError) -> Self {
let data = ErrorData::from(&e);
let info = data.info.clone();
Error::new(
ErrorData::from(&e),
data,
if let Ok(kind) = e.code.try_into() {
kind
} else if e.code == METHOD_NOT_FOUND_ERROR.code {
@@ -523,6 +525,7 @@ impl From<RpcError> for Error {
ErrorKind::Unknown
},
)
.with_info(info)
}
}

View File

@@ -524,26 +524,26 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
"To access your Web URL securely, trust your Root CA (displayed above) on your client device(s):\n",
" - MacOS\n",
" 1. Open the Terminal app\n",
" 2. Paste the following command (**DO NOTt** click Return): pbcopy < ~/Desktop/ca.crt\n",
" 2. Paste the following command (**DO NOT** click Return): pbcopy < ~/Desktop/ca.crt\n",
" 3. Copy your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
" 4. Back in Terminal, click Return. ca.crt is saved to your Desktop\n",
" 5. Complete by trusting your Root CA: https://https://staging.docs.start9.com/device-guides/mac/ca.html\n",
" 5. Complete by trusting your Root CA: https://staging.docs.start9.com/device-guides/mac/ca.html\n",
" - Linux\n",
" 1. Open gedit, nano, or any editor\n",
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
" 3. Name the file ca.crt and save as plaintext\n",
" 5. Complete by trusting your Root CA: https://https://staging.docs.start9.com/device-guides/linux/ca.html\n",
" 5. Complete by trusting your Root CA: https://staging.docs.start9.com/device-guides/linux/ca.html\n",
" - Windows\n",
" 1. Open the Notepad app\n",
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
" 3. Name the file ca.crt and save as plaintext\n",
" 5. Complete by trusting your Root CA: https://https://staging.docs.start9.com/device-guides/windows/ca.html\n",
" 5. Complete by trusting your Root CA: https://staging.docs.start9.com/device-guides/windows/ca.html\n",
" - Android/Graphene\n",
" 1. Send the ca.crt file (created above) to yourself\n",
" 2. Complete by trusting your Root CA: https://https://staging.docs.start9.com/device-guides/android/ca.html\n",
" 2. Complete by trusting your Root CA: https://staging.docs.start9.com/device-guides/android/ca.html\n",
" - iOS\n",
" 1. Send the ca.crt file (created above) to yourself\n",
" 2. Complete by trusting your Root CA: https://https://staging.docs.start9.com/device-guides/ios/ca.html\n",
" 2. Complete by trusting your Root CA: https://staging.docs.start9.com/device-guides/ios/ca.html\n",
));
return Ok(());

View File

@@ -16,14 +16,14 @@ import {
MountParams,
StatusInfo,
Manifest,
} from "./osBindings"
} from './osBindings'
import {
PackageId,
Dependencies,
ServiceInterfaceId,
SmtpValue,
ActionResult,
} from "./types"
} from './types'
/** Used to reach out from the pure js runtime */
@@ -155,13 +155,13 @@ export type Effects = {
/** Returns a PEM encoded fullchain for the hostnames specified */
getSslCertificate: (options: {
hostnames: string[]
algorithm?: "ecdsa" | "ed25519"
algorithm?: 'ecdsa' | 'ed25519'
callback?: () => void
}) => Promise<[string, string, string]>
/** Returns a PEM encoded private key corresponding to the certificate for the hostnames specified */
getSslKey: (options: {
hostnames: string[]
algorithm?: "ecdsa" | "ed25519"
algorithm?: 'ecdsa' | 'ed25519'
}) => Promise<string>
/** sets the version that this service's data has been migrated to */

View File

@@ -1,7 +1,7 @@
import * as T from "../types"
import * as IST from "../actions/input/inputSpecTypes"
import { Action, ActionInfo } from "./setupActions"
import { ExtractInputSpecType } from "./input/builder/inputSpec"
import * as T from '../types'
import * as IST from '../actions/input/inputSpecTypes'
import { Action, ActionInfo } from './setupActions'
import { ExtractInputSpecType } from './input/builder/inputSpec'
export type RunActionInput<Input> =
| Input
@@ -53,17 +53,17 @@ type TaskBase = {
replayId?: string
}
type TaskInput<T extends ActionInfo<T.ActionId, any>> = {
kind: "partial"
kind: 'partial'
value: T.DeepPartial<GetActionInputType<T>>
}
export type TaskOptions<T extends ActionInfo<T.ActionId, any>> = TaskBase &
(
| {
when?: Exclude<T.TaskTrigger, { condition: "input-not-matches" }>
when?: Exclude<T.TaskTrigger, { condition: 'input-not-matches' }>
input?: TaskInput<T>
}
| {
when: T.TaskTrigger & { condition: "input-not-matches" }
when: T.TaskTrigger & { condition: 'input-not-matches' }
input: TaskInput<T>
}
)

View File

@@ -1,6 +1,6 @@
import { InputSpec } from "./inputSpec"
import { List } from "./list"
import { Value } from "./value"
import { Variants } from "./variants"
import { InputSpec } from './inputSpec'
import { List } from './list'
import { Value } from './value'
import { Variants } from './variants'
export { InputSpec as InputSpec, List, Value, Variants }

View File

@@ -1,9 +1,9 @@
import { ValueSpec } from "../inputSpecTypes"
import { Value } from "./value"
import { _ } from "../../../util"
import { Effects } from "../../../Effects"
import { Parser, object } from "ts-matches"
import { DeepPartial } from "../../../types"
import { ValueSpec } from '../inputSpecTypes'
import { Value } from './value'
import { _ } from '../../../util'
import { Effects } from '../../../Effects'
import { Parser, object } from 'ts-matches'
import { DeepPartial } from '../../../types'
export type LazyBuildOptions = {
effects: Effects

View File

@@ -1,4 +1,4 @@
import { InputSpec, LazyBuild } from "./inputSpec"
import { InputSpec, LazyBuild } from './inputSpec'
import {
ListValueSpecText,
Pattern,
@@ -6,8 +6,8 @@ import {
UniqueBy,
ValueSpecList,
ValueSpecListOf,
} from "../inputSpecTypes"
import { Parser, arrayOf, string } from "ts-matches"
} from '../inputSpecTypes'
import { Parser, arrayOf, string } from 'ts-matches'
export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
private constructor(
@@ -55,7 +55,7 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ListValueSpecText["inputmode"]
inputmode?: ListValueSpecText['inputmode']
/**
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
*/
@@ -65,21 +65,21 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const validator = arrayOf(string)
return new List<string[]>(() => {
const spec = {
type: "text" as const,
type: 'text' as const,
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: "text" as const,
inputmode: 'text' as const,
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
}
const built: ValueSpecListOf<"text"> = {
const built: ValueSpecListOf<'text'> = {
description: null,
warning: null,
default: [],
type: "list" as const,
type: 'list' as const,
minLength: null,
maxLength: null,
disabled: false,
@@ -106,7 +106,7 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
minLength?: number | null
maxLength?: number | null
patterns?: Pattern[]
inputmode?: ListValueSpecText["inputmode"]
inputmode?: ListValueSpecText['inputmode']
}
}>,
) {
@@ -114,21 +114,21 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new List<string[]>(async (options) => {
const { spec: aSpec, ...a } = await getA(options)
const spec = {
type: "text" as const,
type: 'text' as const,
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: "text" as const,
inputmode: 'text' as const,
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
}
const built: ValueSpecListOf<"text"> = {
const built: ValueSpecListOf<'text'> = {
description: null,
warning: null,
default: [],
type: "list" as const,
type: 'list' as const,
minLength: null,
maxLength: null,
disabled: false,
@@ -162,7 +162,7 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const { spec: previousSpecSpec, ...restSpec } = aSpec
const built = await previousSpecSpec.build(options)
const spec = {
type: "object" as const,
type: 'object' as const,
displayAs: null,
uniqueBy: null,
...restSpec,
@@ -179,7 +179,7 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
warning: null,
minLength: null,
maxLength: null,
type: "list" as const,
type: 'list' as const,
disabled: false,
...value,
},

View File

@@ -1,6 +1,6 @@
import { InputSpec, LazyBuild } from "./inputSpec"
import { List } from "./list"
import { UnionRes, UnionResStaticValidatedAs, Variants } from "./variants"
import { InputSpec, LazyBuild } from './inputSpec'
import { List } from './list'
import { UnionRes, UnionResStaticValidatedAs, Variants } from './variants'
import {
Pattern,
RandomString,
@@ -9,9 +9,9 @@ import {
ValueSpecHidden,
ValueSpecText,
ValueSpecTextarea,
} from "../inputSpecTypes"
import { DefaultString } from "../inputSpecTypes"
import { _, once } from "../../../util"
} from '../inputSpecTypes'
import { DefaultString } from '../inputSpecTypes'
import { _, once } from '../../../util'
import {
Parser,
any,
@@ -23,8 +23,8 @@ import {
number,
object,
string,
} from "ts-matches"
import { DeepPartial } from "../../../types"
} from 'ts-matches'
import { DeepPartial } from '../../../types'
export const fileInfoParser = object({
path: string,
@@ -42,7 +42,7 @@ const testForAsRequiredParser = once(
function asRequiredParser<Type, Input extends { required: boolean }>(
parser: Parser<unknown, Type>,
input: Input,
): Parser<unknown, AsRequired<Type, Input["required"]>> {
): Parser<unknown, AsRequired<Type, Input['required']>> {
if (testForAsRequiredParser()(input)) return parser as any
return parser.nullable() as any
}
@@ -92,7 +92,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
spec: {
description: null,
warning: null,
type: "toggle" as const,
type: 'toggle' as const,
disabled: false,
immutable: a.immutable ?? false,
...a,
@@ -117,7 +117,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
spec: {
description: null,
warning: null,
type: "toggle" as const,
type: 'toggle' as const,
disabled: false,
immutable: false,
...(await a(options)),
@@ -191,7 +191,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ValueSpecText["inputmode"]
inputmode?: ValueSpecText['inputmode']
/**
* @description Once set, the value can never be changed.
* @default false
@@ -206,7 +206,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<AsRequired<string, Required>>(
async () => ({
spec: {
type: "text" as const,
type: 'text' as const,
description: null,
warning: null,
masked: false,
@@ -214,7 +214,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
inputmode: 'text',
disabled: false,
immutable: a.immutable ?? false,
generate: a.generate ?? null,
@@ -237,7 +237,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
minLength?: number | null
maxLength?: number | null
patterns?: Pattern[]
inputmode?: ValueSpecText["inputmode"]
inputmode?: ValueSpecText['inputmode']
disabled?: string | false
generate?: null | RandomString
}>,
@@ -247,7 +247,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const a = await getA(options)
return {
spec: {
type: "text" as const,
type: 'text' as const,
description: null,
warning: null,
masked: false,
@@ -255,7 +255,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
inputmode: 'text',
disabled: false,
immutable: false,
generate: a.generate ?? null,
@@ -334,7 +334,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
minRows: 3,
maxRows: 6,
placeholder: null,
type: "textarea" as const,
type: 'textarea' as const,
disabled: false,
immutable: a.immutable ?? false,
...a,
@@ -371,7 +371,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
minRows: 3,
maxRows: 6,
placeholder: null,
type: "textarea" as const,
type: 'textarea' as const,
disabled: false,
immutable: false,
...a,
@@ -444,7 +444,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<AsRequired<number, Required>>(
() => ({
spec: {
type: "number" as const,
type: 'number' as const,
description: null,
warning: null,
min: null,
@@ -482,7 +482,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const a = await getA(options)
return {
spec: {
type: "number" as const,
type: 'number' as const,
description: null,
warning: null,
min: null,
@@ -540,7 +540,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<AsRequired<string, Required>>(
() => ({
spec: {
type: "color" as const,
type: 'color' as const,
description: null,
warning: null,
disabled: false,
@@ -568,7 +568,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const a = await getA(options)
return {
spec: {
type: "color" as const,
type: 'color' as const,
description: null,
warning: null,
disabled: false,
@@ -618,7 +618,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
* @description Informs the browser how to behave and which date/time component to display.
* @default "datetime-local"
*/
inputmode?: ValueSpecDatetime["inputmode"]
inputmode?: ValueSpecDatetime['inputmode']
min?: string | null
max?: string | null
/**
@@ -631,10 +631,10 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<AsRequired<string, Required>>(
() => ({
spec: {
type: "datetime" as const,
type: 'datetime' as const,
description: null,
warning: null,
inputmode: "datetime-local",
inputmode: 'datetime-local',
min: null,
max: null,
step: null,
@@ -654,7 +654,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
warning?: string | null
default: string | null
required: Required
inputmode?: ValueSpecDatetime["inputmode"]
inputmode?: ValueSpecDatetime['inputmode']
min?: string | null
max?: string | null
disabled?: false | string
@@ -665,10 +665,10 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const a = await getA(options)
return {
spec: {
type: "datetime" as const,
type: 'datetime' as const,
description: null,
warning: null,
inputmode: "datetime-local",
inputmode: 'datetime-local',
min: null,
max: null,
disabled: false,
@@ -740,7 +740,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
spec: {
description: null,
warning: null,
type: "select" as const,
type: 'select' as const,
disabled: false,
immutable: a.immutable ?? false,
...a,
@@ -766,7 +766,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
spec: {
description: null,
warning: null,
type: "select" as const,
type: 'select' as const,
disabled: false,
immutable: false,
...a,
@@ -837,7 +837,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<(keyof Values & string)[]>(
() => ({
spec: {
type: "multiselect" as const,
type: 'multiselect' as const,
minLength: null,
maxLength: null,
warning: null,
@@ -867,7 +867,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const a = await getA(options)
return {
spec: {
type: "multiselect" as const,
type: 'multiselect' as const,
minLength: null,
maxLength: null,
warning: null,
@@ -915,7 +915,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const built = await spec.build(options as any)
return {
spec: {
type: "object" as const,
type: 'object' as const,
description: null,
warning: null,
...a,
@@ -933,7 +933,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
required: Required
}) {
const buildValue = {
type: "file" as const,
type: 'file' as const,
description: null,
warning: null,
...a,
@@ -960,7 +960,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<AsRequired<FileInfo, Required>, FileInfo | null>(
async (options) => {
const spec = {
type: "file" as const,
type: 'file' as const,
description: null,
warning: null,
...(await a(options)),
@@ -1034,7 +1034,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const built = await a.variants.build(options as any)
return {
spec: {
type: "union" as const,
type: 'union' as const,
description: null,
warning: null,
disabled: false,
@@ -1109,7 +1109,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const built = await newValues.variants.build(options as any)
return {
spec: {
type: "union" as const,
type: 'union' as const,
description: null,
warning: null,
...newValues,
@@ -1202,7 +1202,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
return new Value<T, typeof parser._TYPE>(async () => {
return {
spec: {
type: "hidden" as const,
type: 'hidden' as const,
} as ValueSpecHidden,
validator: parser,
}
@@ -1221,7 +1221,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
const validator = await getParser(options)
return {
spec: {
type: "hidden" as const,
type: 'hidden' as const,
} as ValueSpecHidden,
validator,
}

View File

@@ -1,12 +1,12 @@
import { DeepPartial } from "../../../types"
import { ValueSpec, ValueSpecUnion } from "../inputSpecTypes"
import { DeepPartial } from '../../../types'
import { ValueSpec, ValueSpecUnion } from '../inputSpecTypes'
import {
LazyBuild,
InputSpec,
ExtractInputSpecType,
ExtractInputSpecStaticValidatedAs,
} from "./inputSpec"
import { Parser, any, anyOf, literal, object } from "ts-matches"
} from './inputSpec'
import { Parser, any, anyOf, literal, object } from 'ts-matches'
export type UnionRes<
VariantValues extends {
@@ -19,10 +19,10 @@ export type UnionRes<
> = {
[key in keyof VariantValues]: {
selection: key
value: ExtractInputSpecType<VariantValues[key]["spec"]>
value: ExtractInputSpecType<VariantValues[key]['spec']>
other?: {
[key2 in Exclude<keyof VariantValues & string, key>]?: DeepPartial<
ExtractInputSpecType<VariantValues[key2]["spec"]>
ExtractInputSpecType<VariantValues[key2]['spec']>
>
}
}
@@ -39,10 +39,10 @@ export type UnionResStaticValidatedAs<
> = {
[key in keyof VariantValues]: {
selection: key
value: ExtractInputSpecStaticValidatedAs<VariantValues[key]["spec"]>
value: ExtractInputSpecStaticValidatedAs<VariantValues[key]['spec']>
other?: {
[key2 in Exclude<keyof VariantValues & string, key>]?: DeepPartial<
ExtractInputSpecStaticValidatedAs<VariantValues[key2]["spec"]>
ExtractInputSpecStaticValidatedAs<VariantValues[key2]['spec']>
>
}
}
@@ -106,7 +106,7 @@ export class Variants<
> {
private constructor(
public build: LazyBuild<{
spec: ValueSpecUnion["variants"]
spec: ValueSpecUnion['variants']
validator: Parser<unknown, UnionRes<VariantValues>>
}>,
public readonly validator: Parser<
@@ -126,7 +126,7 @@ export class Variants<
const staticValidators = {} as {
[K in keyof VariantValues]: Parser<
unknown,
ExtractInputSpecStaticValidatedAs<VariantValues[K]["spec"]>
ExtractInputSpecStaticValidatedAs<VariantValues[K]['spec']>
>
}
for (const key in a) {
@@ -143,7 +143,7 @@ export class Variants<
const validators = {} as {
[K in keyof VariantValues]: Parser<
unknown,
ExtractInputSpecType<VariantValues[K]["spec"]>
ExtractInputSpecType<VariantValues[K]['spec']>
>
}
const variants = {} as {

View File

@@ -1,3 +1,3 @@
export * as constants from "./inputSpecConstants"
export * as types from "./inputSpecTypes"
export * as builder from "./builder"
export * as constants from './inputSpecConstants'
export * as types from './inputSpecTypes'
export * as builder from './builder'

View File

@@ -1,8 +1,8 @@
import { SmtpValue } from "../../types"
import { GetSystemSmtp, Patterns } from "../../util"
import { InputSpec, InputSpecOf } from "./builder/inputSpec"
import { Value } from "./builder/value"
import { Variants } from "./builder/variants"
import { SmtpValue } from '../../types'
import { GetSystemSmtp, Patterns } from '../../util'
import { InputSpec, InputSpecOf } from './builder/inputSpec'
import { Value } from './builder/value'
import { Variants } from './builder/variants'
/**
* Base SMTP settings, to be used by StartOS for system wide SMTP
@@ -11,12 +11,12 @@ export const customSmtp: InputSpec<SmtpValue> = InputSpec.of<
InputSpecOf<SmtpValue>
>({
server: Value.text({
name: "SMTP Server",
name: 'SMTP Server',
required: true,
default: null,
}),
port: Value.number({
name: "Port",
name: 'Port',
required: true,
default: 587,
min: 1,
@@ -24,20 +24,20 @@ export const customSmtp: InputSpec<SmtpValue> = InputSpec.of<
integer: true,
}),
from: Value.text({
name: "From Address",
name: 'From Address',
required: true,
default: null,
placeholder: "Example Name <test@example.com>",
inputmode: "email",
placeholder: 'Example Name <test@example.com>',
inputmode: 'email',
patterns: [Patterns.emailWithName],
}),
login: Value.text({
name: "Login",
name: 'Login',
required: true,
default: null,
}),
password: Value.text({
name: "Password",
name: 'Password',
required: false,
default: null,
masked: true,
@@ -45,24 +45,24 @@ export const customSmtp: InputSpec<SmtpValue> = InputSpec.of<
})
const smtpVariants = Variants.of({
disabled: { name: "Disabled", spec: InputSpec.of({}) },
disabled: { name: 'Disabled', spec: InputSpec.of({}) },
system: {
name: "System Credentials",
name: 'System Credentials',
spec: InputSpec.of({
customFrom: Value.text({
name: "Custom From Address",
name: 'Custom From Address',
description:
"A custom from address for this service. If not provided, the system from address will be used.",
'A custom from address for this service. If not provided, the system from address will be used.',
required: false,
default: null,
placeholder: "<name>test@example.com",
inputmode: "email",
placeholder: '<name>test@example.com',
inputmode: 'email',
patterns: [Patterns.email],
}),
}),
},
custom: {
name: "Custom Credentials",
name: 'Custom Credentials',
spec: customSmtp,
},
})
@@ -71,11 +71,11 @@ const smtpVariants = Variants.of({
*/
export const smtpInputSpec = Value.dynamicUnion(async ({ effects }) => {
const smtp = await new GetSystemSmtp(effects).once()
const disabled = smtp ? [] : ["system"]
const disabled = smtp ? [] : ['system']
return {
name: "SMTP",
description: "Optionally provide an SMTP server for sending emails",
default: "disabled",
name: 'SMTP',
description: 'Optionally provide an SMTP server for sending emails',
default: 'disabled',
disabled,
variants: smtpVariants,
}

View File

@@ -1,18 +1,18 @@
export type InputSpec = Record<string, ValueSpec>
export type ValueType =
| "text"
| "textarea"
| "number"
| "color"
| "datetime"
| "toggle"
| "select"
| "multiselect"
| "list"
| "object"
| "file"
| "union"
| "hidden"
| 'text'
| 'textarea'
| 'number'
| 'color'
| 'datetime'
| 'toggle'
| 'select'
| 'multiselect'
| 'list'
| 'object'
| 'file'
| 'union'
| 'hidden'
export type ValueSpec = ValueSpecOf<ValueType>
/** core spec types. These types provide the metadata for performing validations */
// prettier-ignore
@@ -37,13 +37,13 @@ export type ValueSpecText = {
description: string | null
warning: string | null
type: "text"
type: 'text'
patterns: Pattern[]
minLength: number | null
maxLength: number | null
masked: boolean
inputmode: "text" | "email" | "tel" | "url"
inputmode: 'text' | 'email' | 'tel' | 'url'
placeholder: string | null
required: boolean
@@ -57,7 +57,7 @@ export type ValueSpecTextarea = {
description: string | null
warning: string | null
type: "textarea"
type: 'textarea'
patterns: Pattern[]
placeholder: string | null
minLength: number | null
@@ -71,7 +71,7 @@ export type ValueSpecTextarea = {
}
export type ValueSpecNumber = {
type: "number"
type: 'number'
min: number | null
max: number | null
integer: boolean
@@ -91,7 +91,7 @@ export type ValueSpecColor = {
description: string | null
warning: string | null
type: "color"
type: 'color'
required: boolean
default: string | null
disabled: false | string
@@ -101,9 +101,9 @@ export type ValueSpecDatetime = {
name: string
description: string | null
warning: string | null
type: "datetime"
type: 'datetime'
required: boolean
inputmode: "date" | "time" | "datetime-local"
inputmode: 'date' | 'time' | 'datetime-local'
min: string | null
max: string | null
default: string | null
@@ -115,7 +115,7 @@ export type ValueSpecSelect = {
name: string
description: string | null
warning: string | null
type: "select"
type: 'select'
default: string | null
disabled: false | string | string[]
immutable: boolean
@@ -127,7 +127,7 @@ export type ValueSpecMultiselect = {
description: string | null
warning: string | null
type: "multiselect"
type: 'multiselect'
minLength: number | null
maxLength: number | null
disabled: false | string | string[]
@@ -139,7 +139,7 @@ export type ValueSpecToggle = {
description: string | null
warning: string | null
type: "toggle"
type: 'toggle'
default: boolean | null
disabled: false | string
immutable: boolean
@@ -149,7 +149,7 @@ export type ValueSpecUnion = {
description: string | null
warning: string | null
type: "union"
type: 'union'
variants: Record<
string,
{
@@ -165,7 +165,7 @@ export type ValueSpecFile = {
name: string
description: string | null
warning: string | null
type: "file"
type: 'file'
extensions: string[]
required: boolean
}
@@ -173,13 +173,13 @@ export type ValueSpecObject = {
name: string
description: string | null
warning: string | null
type: "object"
type: 'object'
spec: InputSpec
}
export type ValueSpecHidden = {
type: "hidden"
type: 'hidden'
}
export type ListValueSpecType = "text" | "object"
export type ListValueSpecType = 'text' | 'object'
// prettier-ignore
export type ListValueSpecOf<T extends ListValueSpecType> =
T extends "text" ? ListValueSpecText :
@@ -190,7 +190,7 @@ export type ValueSpecListOf<T extends ListValueSpecType> = {
name: string
description: string | null
warning: string | null
type: "list"
type: 'list'
spec: ListValueSpecOf<T>
minLength: number | null
maxLength: number | null
@@ -208,18 +208,18 @@ export type Pattern = {
description: string
}
export type ListValueSpecText = {
type: "text"
type: 'text'
patterns: Pattern[]
minLength: number | null
maxLength: number | null
masked: boolean
generate: null | RandomString
inputmode: "text" | "email" | "tel" | "url"
inputmode: 'text' | 'email' | 'tel' | 'url'
placeholder: string | null
}
export type ListValueSpecObject = {
type: "object"
type: 'object'
spec: InputSpec
uniqueBy: UniqueBy
displayAs: string | null
@@ -244,5 +244,5 @@ export function isValueSpecListOf<S extends ListValueSpecType>(
t: ValueSpec,
s: S,
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
return "spec" in t && t.spec.type === s
return 'spec' in t && t.spec.type === s
}

View File

@@ -1,16 +1,16 @@
import { InputSpec } from "./input/builder"
import { ExtractInputSpecType } from "./input/builder/inputSpec"
import * as T from "../types"
import { once } from "../util"
import { InitScript } from "../inits"
import { Parser } from "ts-matches"
import { InputSpec } from './input/builder'
import { ExtractInputSpecType } from './input/builder/inputSpec'
import * as T from '../types'
import { once } from '../util'
import { InitScript } from '../inits'
import { Parser } from 'ts-matches'
type MaybeInputSpec<Type> = {} extends Type ? null : InputSpec<Type>
export type Run<A extends Record<string, any>> = (options: {
effects: T.Effects
input: A
spec: T.inputSpecTypes.InputSpec
}) => Promise<(T.ActionResult & { version: "1" }) | null | void | undefined>
}) => Promise<(T.ActionResult & { version: '1' }) | null | void | undefined>
export type GetInput<A extends Record<string, any>> = (options: {
effects: T.Effects
}) => Promise<null | void | undefined | T.DeepPartial<A>>
@@ -65,7 +65,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
InputSpecType extends InputSpec<Record<string, any>>,
>(
id: Id,
metadata: MaybeFn<Omit<T.ActionMetadata, "hasInput">>,
metadata: MaybeFn<Omit<T.ActionMetadata, 'hasInput'>>,
inputSpec: InputSpecType,
getInput: GetInput<ExtractInputSpecType<InputSpecType>>,
run: Run<ExtractInputSpecType<InputSpecType>>,
@@ -80,7 +80,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
}
static withoutInput<Id extends T.ActionId>(
id: Id,
metadata: MaybeFn<Omit<T.ActionMetadata, "hasInput">>,
metadata: MaybeFn<Omit<T.ActionMetadata, 'hasInput'>>,
run: Run<{}>,
): Action<Id, {}> {
return new Action(
@@ -156,7 +156,7 @@ export class Actions<
}
addAction<A extends Action<T.ActionId, any>>(
action: A, // TODO: prevent duplicates
): Actions<AllActions & { [id in A["id"]]: A }> {
): Actions<AllActions & { [id in A['id']]: A }> {
return new Actions({ ...this.actions, [action.id]: action })
}
async init(effects: T.Effects): Promise<void> {

View File

@@ -1,11 +1,11 @@
import { ExtendedVersion, VersionRange } from "../exver"
import { ExtendedVersion, VersionRange } from '../exver'
import {
PackageId,
HealthCheckId,
DependencyRequirement,
CheckDependenciesResult,
} from "../types"
import { Effects } from "../Effects"
} from '../types'
import { Effects } from '../Effects'
export type CheckDependencies<DependencyId extends PackageId = PackageId> = {
infoFor: (packageId: DependencyId) => {
@@ -73,11 +73,11 @@ export async function checkDependencies<
}
const runningSatisfied = (packageId: DependencyId) => {
const dep = infoFor(packageId)
return dep.requirement.kind !== "running" || dep.result.isRunning
return dep.requirement.kind !== 'running' || dep.result.isRunning
}
const tasksSatisfied = (packageId: DependencyId) =>
Object.entries(infoFor(packageId).result.tasks).filter(
([_, t]) => t?.active && t.task.severity === "critical",
([_, t]) => t?.active && t.task.severity === 'critical',
).length === 0
const healthCheckSatisfied = (
packageId: DependencyId,
@@ -86,17 +86,17 @@ export async function checkDependencies<
const dep = infoFor(packageId)
if (
healthCheckId &&
(dep.requirement.kind !== "running" ||
(dep.requirement.kind !== 'running' ||
!dep.requirement.healthChecks.includes(healthCheckId))
) {
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
}
const errors =
dep.requirement.kind === "running"
dep.requirement.kind === 'running'
? dep.requirement.healthChecks
.map((id) => [id, dep.result.healthChecks[id] ?? null] as const)
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
.filter(([_, res]) => res?.result !== "success")
.filter(([_, res]) => res?.result !== 'success')
: []
return errors.length === 0
}
@@ -138,7 +138,7 @@ export async function checkDependencies<
}
const throwIfRunningNotSatisfied = (packageId: DependencyId) => {
const dep = infoFor(packageId)
if (dep.requirement.kind === "running" && !dep.result.isRunning) {
if (dep.requirement.kind === 'running' && !dep.result.isRunning) {
throw new Error(`${dep.result.title || packageId} is not running`)
}
return null
@@ -146,11 +146,11 @@ export async function checkDependencies<
const throwIfTasksNotSatisfied = (packageId: DependencyId) => {
const dep = infoFor(packageId)
const reqs = Object.entries(dep.result.tasks)
.filter(([_, t]) => t?.active && t.task.severity === "critical")
.filter(([_, t]) => t?.active && t.task.severity === 'critical')
.map(([id, _]) => id)
if (reqs.length) {
throw new Error(
`The following action requests have not been fulfilled: ${reqs.join(", ")}`,
`The following action requests have not been fulfilled: ${reqs.join(', ')}`,
)
}
return null
@@ -162,27 +162,27 @@ export async function checkDependencies<
const dep = infoFor(packageId)
if (
healthCheckId &&
(dep.requirement.kind !== "running" ||
(dep.requirement.kind !== 'running' ||
!dep.requirement.healthChecks.includes(healthCheckId))
) {
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
}
const errors =
dep.requirement.kind === "running"
dep.requirement.kind === 'running'
? dep.requirement.healthChecks
.map((id) => [id, dep.result.healthChecks[id] ?? null] as const)
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
.filter(([_, res]) => res?.result !== "success")
.filter(([_, res]) => res?.result !== 'success')
: []
if (errors.length) {
throw new Error(
errors
.map(([id, e]) =>
e
? `Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ""}`
? `Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ''}`
: `Health Check ${id} of ${dep.result.title} does not exist`,
)
.join("; "),
.join('; '),
)
}
return null
@@ -209,7 +209,7 @@ export async function checkDependencies<
return []
})
if (err.length) {
throw new Error(err.join("; "))
throw new Error(err.join('; '))
}
return null
})()

View File

@@ -1,27 +1,27 @@
import * as T from "../types"
import { once } from "../util"
import * as T from '../types'
import { once } from '../util'
export type RequiredDependenciesOf<Manifest extends T.SDKManifest> = {
[K in keyof Manifest["dependencies"]]: Exclude<
Manifest["dependencies"][K],
[K in keyof Manifest['dependencies']]: Exclude<
Manifest['dependencies'][K],
undefined
>["optional"] extends false
>['optional'] extends false
? K
: never
}[keyof Manifest["dependencies"]]
}[keyof Manifest['dependencies']]
export type OptionalDependenciesOf<Manifest extends T.SDKManifest> = Exclude<
keyof Manifest["dependencies"],
keyof Manifest['dependencies'],
RequiredDependenciesOf<Manifest>
>
type DependencyRequirement =
| {
kind: "running"
kind: 'running'
healthChecks: Array<T.HealthCheckId>
versionRange: string
}
| {
kind: "exists"
kind: 'exists'
versionRange: string
}
type Matches<T, U> = T extends U ? (U extends T ? null : never) : never

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { DeepMap } from "deep-equality-data-structures"
import * as P from "./exver"
import { DeepMap } from 'deep-equality-data-structures'
import * as P from './exver'
// prettier-ignore
export type ValidateVersion<T extends String> =
@@ -22,35 +22,35 @@ export type ValidateExVers<T> =
never[]
type Anchor = {
type: "Anchor"
type: 'Anchor'
operator: P.CmpOp
version: ExtendedVersion
}
type And = {
type: "And"
type: 'And'
left: VersionRange
right: VersionRange
}
type Or = {
type: "Or"
type: 'Or'
left: VersionRange
right: VersionRange
}
type Not = {
type: "Not"
type: 'Not'
value: VersionRange
}
type Flavor = {
type: "Flavor"
type: 'Flavor'
flavor: string | null
}
type FlavorNot = {
type: "FlavorNot"
type: 'FlavorNot'
flavors: Set<string | null>
}
@@ -107,8 +107,8 @@ function adjacentVersionRangePoints(
}
function flavorAnd(a: FlavorAtom, b: FlavorAtom): FlavorAtom | null {
if (a.type == "Flavor") {
if (b.type == "Flavor") {
if (a.type == 'Flavor') {
if (b.type == 'Flavor') {
if (a.flavor == b.flavor) {
return a
} else {
@@ -122,7 +122,7 @@ function flavorAnd(a: FlavorAtom, b: FlavorAtom): FlavorAtom | null {
}
}
} else {
if (b.type == "Flavor") {
if (b.type == 'Flavor') {
if (a.flavors.has(b.flavor)) {
return null
} else {
@@ -131,7 +131,7 @@ function flavorAnd(a: FlavorAtom, b: FlavorAtom): FlavorAtom | null {
} else {
// TODO: use Set.union if targeting esnext or later
return {
type: "FlavorNot",
type: 'FlavorNot',
flavors: new Set([...a.flavors, ...b.flavors]),
}
}
@@ -218,12 +218,12 @@ class VersionRangeTable {
static eqFlavor(flavor: string | null): VersionRangeTables {
return new DeepMap([
[
{ type: "Flavor", flavor } as FlavorAtom,
{ type: 'Flavor', flavor } as FlavorAtom,
new VersionRangeTable([], [true]),
],
// make sure the truth table is exhaustive, or `not` will not work properly.
[
{ type: "FlavorNot", flavors: new Set([flavor]) } as FlavorAtom,
{ type: 'FlavorNot', flavors: new Set([flavor]) } as FlavorAtom,
new VersionRangeTable([], [false]),
],
])
@@ -241,12 +241,12 @@ class VersionRangeTable {
): VersionRangeTables {
return new DeepMap([
[
{ type: "Flavor", flavor } as FlavorAtom,
{ type: 'Flavor', flavor } as FlavorAtom,
new VersionRangeTable([point], [left, right]),
],
// make sure the truth table is exhaustive, or `not` will not work properly.
[
{ type: "FlavorNot", flavors: new Set([flavor]) } as FlavorAtom,
{ type: 'FlavorNot', flavors: new Set([flavor]) } as FlavorAtom,
new VersionRangeTable([], [false]),
],
])
@@ -383,7 +383,7 @@ class VersionRangeTable {
let sum_terms: VersionRange[] = []
for (let [flavor, table] of tables) {
let cmp_flavor = null
if (flavor.type == "Flavor") {
if (flavor.type == 'Flavor') {
cmp_flavor = flavor.flavor
}
for (let i = 0; i < table.values.length; i++) {
@@ -392,7 +392,7 @@ class VersionRangeTable {
continue
}
if (flavor.type == "FlavorNot") {
if (flavor.type == 'FlavorNot') {
for (let not_flavor of flavor.flavors) {
term.push(VersionRange.flavor(not_flavor).not())
}
@@ -410,7 +410,7 @@ class VersionRangeTable {
if (p != null && q != null && adjacentVersionRangePoints(p, q)) {
term.push(
VersionRange.anchor(
"=",
'=',
new ExtendedVersion(cmp_flavor, p.upstream, p.downstream),
),
)
@@ -418,7 +418,7 @@ class VersionRangeTable {
if (p != null && p.side < 0) {
term.push(
VersionRange.anchor(
">=",
'>=',
new ExtendedVersion(cmp_flavor, p.upstream, p.downstream),
),
)
@@ -426,7 +426,7 @@ class VersionRangeTable {
if (p != null && p.side >= 0) {
term.push(
VersionRange.anchor(
">",
'>',
new ExtendedVersion(cmp_flavor, p.upstream, p.downstream),
),
)
@@ -434,7 +434,7 @@ class VersionRangeTable {
if (q != null && q.side < 0) {
term.push(
VersionRange.anchor(
"<",
'<',
new ExtendedVersion(cmp_flavor, q.upstream, q.downstream),
),
)
@@ -442,7 +442,7 @@ class VersionRangeTable {
if (q != null && q.side >= 0) {
term.push(
VersionRange.anchor(
"<=",
'<=',
new ExtendedVersion(cmp_flavor, q.upstream, q.downstream),
),
)
@@ -463,26 +463,26 @@ class VersionRangeTable {
export class VersionRange {
constructor(public atom: Anchor | And | Or | Not | P.Any | P.None | Flavor) {}
toStringParens(parent: "And" | "Or" | "Not") {
toStringParens(parent: 'And' | 'Or' | 'Not') {
let needs = true
switch (this.atom.type) {
case "And":
case "Or":
case 'And':
case 'Or':
needs = parent != this.atom.type
break
case "Anchor":
case "Any":
case "None":
needs = parent == "Not"
case 'Anchor':
case 'Any':
case 'None':
needs = parent == 'Not'
break
case "Not":
case "Flavor":
case 'Not':
case 'Flavor':
needs = false
break
}
if (needs) {
return "(" + this.toString() + ")"
return '(' + this.toString() + ')'
} else {
return this.toString()
}
@@ -490,36 +490,36 @@ export class VersionRange {
toString(): string {
switch (this.atom.type) {
case "Anchor":
case 'Anchor':
return `${this.atom.operator}${this.atom.version}`
case "And":
case 'And':
return `${this.atom.left.toStringParens(this.atom.type)} && ${this.atom.right.toStringParens(this.atom.type)}`
case "Or":
case 'Or':
return `${this.atom.left.toStringParens(this.atom.type)} || ${this.atom.right.toStringParens(this.atom.type)}`
case "Not":
case 'Not':
return `!${this.atom.value.toStringParens(this.atom.type)}`
case "Flavor":
case 'Flavor':
return this.atom.flavor == null ? `#` : `#${this.atom.flavor}`
case "Any":
return "*"
case "None":
return "!"
case 'Any':
return '*'
case 'None':
return '!'
}
}
private static parseAtom(atom: P.VersionRangeAtom): VersionRange {
switch (atom.type) {
case "Not":
case 'Not':
return new VersionRange({
type: "Not",
type: 'Not',
value: VersionRange.parseAtom(atom.value),
})
case "Parens":
case 'Parens':
return VersionRange.parseRange(atom.expr)
case "Anchor":
case 'Anchor':
return new VersionRange({
type: "Anchor",
operator: atom.operator || "^",
type: 'Anchor',
operator: atom.operator || '^',
version: new ExtendedVersion(
atom.version.flavor,
new Version(
@@ -532,7 +532,7 @@ export class VersionRange {
),
),
})
case "Flavor":
case 'Flavor':
return VersionRange.flavor(atom.flavor)
default:
return new VersionRange(atom)
@@ -543,17 +543,17 @@ export class VersionRange {
let result = VersionRange.parseAtom(range[0])
for (const next of range[1]) {
switch (next[1]?.[0]) {
case "||":
case '||':
result = new VersionRange({
type: "Or",
type: 'Or',
left: result,
right: VersionRange.parseAtom(next[2]),
})
break
case "&&":
case '&&':
default:
result = new VersionRange({
type: "And",
type: 'And',
left: result,
right: VersionRange.parseAtom(next[2]),
})
@@ -565,49 +565,49 @@ export class VersionRange {
static parse(range: string): VersionRange {
return VersionRange.parseRange(
P.parse(range, { startRule: "VersionRange" }),
P.parse(range, { startRule: 'VersionRange' }),
)
}
static anchor(operator: P.CmpOp, version: ExtendedVersion) {
return new VersionRange({ type: "Anchor", operator, version })
return new VersionRange({ type: 'Anchor', operator, version })
}
static flavor(flavor: string | null) {
return new VersionRange({ type: "Flavor", flavor })
return new VersionRange({ type: 'Flavor', flavor })
}
static parseEmver(range: string): VersionRange {
return VersionRange.parseRange(
P.parse(range, { startRule: "EmverVersionRange" }),
P.parse(range, { startRule: 'EmverVersionRange' }),
)
}
and(right: VersionRange) {
return new VersionRange({ type: "And", left: this, right })
return new VersionRange({ type: 'And', left: this, right })
}
or(right: VersionRange) {
return new VersionRange({ type: "Or", left: this, right })
return new VersionRange({ type: 'Or', left: this, right })
}
not() {
return new VersionRange({ type: "Not", value: this })
return new VersionRange({ type: 'Not', value: this })
}
static and(...xs: Array<VersionRange>) {
let y = VersionRange.any()
for (let x of xs) {
if (x.atom.type == "Any") {
if (x.atom.type == 'Any') {
continue
}
if (x.atom.type == "None") {
if (x.atom.type == 'None') {
return x
}
if (y.atom.type == "Any") {
if (y.atom.type == 'Any') {
y = x
} else {
y = new VersionRange({ type: "And", left: y, right: x })
y = new VersionRange({ type: 'And', left: y, right: x })
}
}
return y
@@ -616,27 +616,27 @@ export class VersionRange {
static or(...xs: Array<VersionRange>) {
let y = VersionRange.none()
for (let x of xs) {
if (x.atom.type == "None") {
if (x.atom.type == 'None') {
continue
}
if (x.atom.type == "Any") {
if (x.atom.type == 'Any') {
return x
}
if (y.atom.type == "None") {
if (y.atom.type == 'None') {
y = x
} else {
y = new VersionRange({ type: "Or", left: y, right: x })
y = new VersionRange({ type: 'Or', left: y, right: x })
}
}
return y
}
static any() {
return new VersionRange({ type: "Any" })
return new VersionRange({ type: 'Any' })
}
static none() {
return new VersionRange({ type: "None" })
return new VersionRange({ type: 'None' })
}
satisfiedBy(version: Version | ExtendedVersion) {
@@ -645,23 +645,23 @@ export class VersionRange {
tables(): VersionRangeTables {
switch (this.atom.type) {
case "Anchor":
case 'Anchor':
switch (this.atom.operator) {
case "=":
case '=':
// `=1.2.3` is equivalent to `>=1.2.3 && <=1.2.4 && #flavor`
return VersionRangeTable.and(
VersionRangeTable.cmp(this.atom.version, -1, false, true),
VersionRangeTable.cmp(this.atom.version, 1, true, false),
)
case ">":
case '>':
return VersionRangeTable.cmp(this.atom.version, 1, false, true)
case "<":
case '<':
return VersionRangeTable.cmp(this.atom.version, -1, true, false)
case ">=":
case '>=':
return VersionRangeTable.cmp(this.atom.version, -1, false, true)
case "<=":
case '<=':
return VersionRangeTable.cmp(this.atom.version, 1, true, false)
case "!=":
case '!=':
// `!=1.2.3` is equivalent to `!(>=1.2.3 && <=1.2.3 && #flavor)`
// **not** equivalent to `(<1.2.3 || >1.2.3) && #flavor`
return VersionRangeTable.not(
@@ -670,7 +670,7 @@ export class VersionRange {
VersionRangeTable.cmp(this.atom.version, 1, true, false),
),
)
case "^":
case '^':
// `^1.2.3` is equivalent to `>=1.2.3 && <2.0.0 && #flavor`
return VersionRangeTable.and(
VersionRangeTable.cmp(this.atom.version, -1, false, true),
@@ -681,7 +681,7 @@ export class VersionRange {
false,
),
)
case "~":
case '~':
// `~1.2.3` is equivalent to `>=1.2.3 && <1.3.0 && #flavor`
return VersionRangeTable.and(
VersionRangeTable.cmp(this.atom.version, -1, false, true),
@@ -693,23 +693,23 @@ export class VersionRange {
),
)
}
case "Flavor":
case 'Flavor':
return VersionRangeTable.eqFlavor(this.atom.flavor)
case "Not":
case 'Not':
return VersionRangeTable.not(this.atom.value.tables())
case "And":
case 'And':
return VersionRangeTable.and(
this.atom.left.tables(),
this.atom.right.tables(),
)
case "Or":
case 'Or':
return VersionRangeTable.or(
this.atom.left.tables(),
this.atom.right.tables(),
)
case "Any":
case 'Any':
return true
case "None":
case 'None':
return false
}
}
@@ -734,23 +734,23 @@ export class Version {
) {}
toString(): string {
return `${this.number.join(".")}${this.prerelease.length > 0 ? `-${this.prerelease.join(".")}` : ""}`
return `${this.number.join('.')}${this.prerelease.length > 0 ? `-${this.prerelease.join('.')}` : ''}`
}
compare(other: Version): "greater" | "equal" | "less" {
compare(other: Version): 'greater' | 'equal' | 'less' {
const numLen = Math.max(this.number.length, other.number.length)
for (let i = 0; i < numLen; i++) {
if ((this.number[i] || 0) > (other.number[i] || 0)) {
return "greater"
return 'greater'
} else if ((this.number[i] || 0) < (other.number[i] || 0)) {
return "less"
return 'less'
}
}
if (this.prerelease.length === 0 && other.prerelease.length !== 0) {
return "greater"
return 'greater'
} else if (this.prerelease.length !== 0 && other.prerelease.length === 0) {
return "less"
return 'less'
}
const prereleaseLen = Math.max(
@@ -760,42 +760,42 @@ export class Version {
for (let i = 0; i < prereleaseLen; i++) {
if (typeof this.prerelease[i] === typeof other.prerelease[i]) {
if (this.prerelease[i] > other.prerelease[i]) {
return "greater"
return 'greater'
} else if (this.prerelease[i] < other.prerelease[i]) {
return "less"
return 'less'
}
} else {
switch (`${typeof this.prerelease[1]}:${typeof other.prerelease[i]}`) {
case "number:string":
return "less"
case "string:number":
return "greater"
case "number:undefined":
case "string:undefined":
return "greater"
case "undefined:number":
case "undefined:string":
return "less"
case 'number:string':
return 'less'
case 'string:number':
return 'greater'
case 'number:undefined':
case 'string:undefined':
return 'greater'
case 'undefined:number':
case 'undefined:string':
return 'less'
}
}
}
return "equal"
return 'equal'
}
compareForSort(other: Version): -1 | 0 | 1 {
switch (this.compare(other)) {
case "greater":
case 'greater':
return 1
case "equal":
case 'equal':
return 0
case "less":
case 'less':
return -1
}
}
static parse(version: string): Version {
const parsed = P.parse(version, { startRule: "Version" })
const parsed = P.parse(version, { startRule: 'Version' })
return new Version(parsed.number, parsed.prerelease)
}
@@ -815,25 +815,25 @@ export class ExtendedVersion {
) {}
toString(): string {
return `${this.flavor ? `#${this.flavor}:` : ""}${this.upstream.toString()}:${this.downstream.toString()}`
return `${this.flavor ? `#${this.flavor}:` : ''}${this.upstream.toString()}:${this.downstream.toString()}`
}
compare(other: ExtendedVersion): "greater" | "equal" | "less" | null {
compare(other: ExtendedVersion): 'greater' | 'equal' | 'less' | null {
if (this.flavor !== other.flavor) {
return null
}
const upstreamCmp = this.upstream.compare(other.upstream)
if (upstreamCmp !== "equal") {
if (upstreamCmp !== 'equal') {
return upstreamCmp
}
return this.downstream.compare(other.downstream)
}
compareLexicographic(other: ExtendedVersion): "greater" | "equal" | "less" {
if ((this.flavor || "") > (other.flavor || "")) {
return "greater"
} else if ((this.flavor || "") > (other.flavor || "")) {
return "less"
compareLexicographic(other: ExtendedVersion): 'greater' | 'equal' | 'less' {
if ((this.flavor || '') > (other.flavor || '')) {
return 'greater'
} else if ((this.flavor || '') > (other.flavor || '')) {
return 'less'
} else {
return this.compare(other)!
}
@@ -841,37 +841,37 @@ export class ExtendedVersion {
compareForSort(other: ExtendedVersion): 1 | 0 | -1 {
switch (this.compareLexicographic(other)) {
case "greater":
case 'greater':
return 1
case "equal":
case 'equal':
return 0
case "less":
case 'less':
return -1
}
}
greaterThan(other: ExtendedVersion): boolean {
return this.compare(other) === "greater"
return this.compare(other) === 'greater'
}
greaterThanOrEqual(other: ExtendedVersion): boolean {
return ["greater", "equal"].includes(this.compare(other) as string)
return ['greater', 'equal'].includes(this.compare(other) as string)
}
equals(other: ExtendedVersion): boolean {
return this.compare(other) === "equal"
return this.compare(other) === 'equal'
}
lessThan(other: ExtendedVersion): boolean {
return this.compare(other) === "less"
return this.compare(other) === 'less'
}
lessThanOrEqual(other: ExtendedVersion): boolean {
return ["less", "equal"].includes(this.compare(other) as string)
return ['less', 'equal'].includes(this.compare(other) as string)
}
static parse(extendedVersion: string): ExtendedVersion {
const parsed = P.parse(extendedVersion, { startRule: "ExtendedVersion" })
const parsed = P.parse(extendedVersion, { startRule: 'ExtendedVersion' })
return new ExtendedVersion(
parsed.flavor || null,
new Version(parsed.upstream.number, parsed.upstream.prerelease),
@@ -881,7 +881,7 @@ export class ExtendedVersion {
static parseEmver(extendedVersion: string): ExtendedVersion {
try {
const parsed = P.parse(extendedVersion, { startRule: "Emver" })
const parsed = P.parse(extendedVersion, { startRule: 'Emver' })
return new ExtendedVersion(
parsed.flavor || null,
new Version(parsed.upstream.number, parsed.upstream.prerelease),
@@ -956,22 +956,22 @@ export class ExtendedVersion {
*/
satisfies(versionRange: VersionRange): boolean {
switch (versionRange.atom.type) {
case "Anchor":
case 'Anchor':
const otherVersion = versionRange.atom.version
switch (versionRange.atom.operator) {
case "=":
case '=':
return this.equals(otherVersion)
case ">":
case '>':
return this.greaterThan(otherVersion)
case "<":
case '<':
return this.lessThan(otherVersion)
case ">=":
case '>=':
return this.greaterThanOrEqual(otherVersion)
case "<=":
case '<=':
return this.lessThanOrEqual(otherVersion)
case "!=":
case '!=':
return !this.equals(otherVersion)
case "^":
case '^':
const nextMajor = versionRange.atom.version.incrementMajor()
if (
this.greaterThanOrEqual(otherVersion) &&
@@ -981,7 +981,7 @@ export class ExtendedVersion {
} else {
return false
}
case "~":
case '~':
const nextMinor = versionRange.atom.version.incrementMinor()
if (
this.greaterThanOrEqual(otherVersion) &&
@@ -992,23 +992,23 @@ export class ExtendedVersion {
return false
}
}
case "Flavor":
case 'Flavor':
return versionRange.atom.flavor == this.flavor
case "And":
case 'And':
return (
this.satisfies(versionRange.atom.left) &&
this.satisfies(versionRange.atom.right)
)
case "Or":
case 'Or':
return (
this.satisfies(versionRange.atom.left) ||
this.satisfies(versionRange.atom.right)
)
case "Not":
case 'Not':
return !this.satisfies(versionRange.atom.value)
case "Any":
case 'Any':
return true
case "None":
case 'None':
return false
}
}
@@ -1020,34 +1020,34 @@ export const testTypeVersion = <T extends string>(t: T & ValidateVersion<T>) =>
t
function tests() {
testTypeVersion("1.2.3")
testTypeVersion("1")
testTypeVersion("12.34.56")
testTypeVersion("1.2-3")
testTypeVersion("1-3")
testTypeVersion("1-alpha")
testTypeVersion('1.2.3')
testTypeVersion('1')
testTypeVersion('12.34.56')
testTypeVersion('1.2-3')
testTypeVersion('1-3')
testTypeVersion('1-alpha')
// @ts-expect-error
testTypeVersion("-3")
testTypeVersion('-3')
// @ts-expect-error
testTypeVersion("1.2.3:1")
testTypeVersion('1.2.3:1')
// @ts-expect-error
testTypeVersion("#cat:1:1")
testTypeVersion('#cat:1:1')
testTypeExVer("1.2.3:1.2.3")
testTypeExVer("1.2.3.4.5.6.7.8.9.0:1")
testTypeExVer("100:1")
testTypeExVer("#cat:1:1")
testTypeExVer("1.2.3.4.5.6.7.8.9.11.22.33:1")
testTypeExVer("1-0:1")
testTypeExVer("1-0:1")
testTypeExVer('1.2.3:1.2.3')
testTypeExVer('1.2.3.4.5.6.7.8.9.0:1')
testTypeExVer('100:1')
testTypeExVer('#cat:1:1')
testTypeExVer('1.2.3.4.5.6.7.8.9.11.22.33:1')
testTypeExVer('1-0:1')
testTypeExVer('1-0:1')
// @ts-expect-error
testTypeExVer("1.2-3")
testTypeExVer('1.2-3')
// @ts-expect-error
testTypeExVer("1-3")
testTypeExVer('1-3')
// @ts-expect-error
testTypeExVer("1.2.3.4.5.6.7.8.9.0.10:1" as string)
testTypeExVer('1.2.3.4.5.6.7.8.9.0.10:1' as string)
// @ts-expect-error
testTypeExVer("1.-2:1")
testTypeExVer('1.-2:1')
// @ts-expect-error
testTypeExVer("1..2.3:3")
testTypeExVer('1..2.3:3')
}

View File

@@ -1,13 +1,13 @@
export { S9pk } from "./s9pk"
export { VersionRange, ExtendedVersion, Version } from "./exver"
export { S9pk } from './s9pk'
export { VersionRange, ExtendedVersion, Version } from './exver'
export * as inputSpec from "./actions/input"
export * as ISB from "./actions/input/builder"
export * as IST from "./actions/input/inputSpecTypes"
export * as types from "./types"
export * as T from "./types"
export * as yaml from "yaml"
export * as inits from "./inits"
export * as matches from "ts-matches"
export * as inputSpec from './actions/input'
export * as ISB from './actions/input/builder'
export * as IST from './actions/input/inputSpecTypes'
export * as types from './types'
export * as T from './types'
export * as yaml from 'yaml'
export * as inits from './inits'
export * as matches from 'ts-matches'
export * as utils from "./util"
export * as utils from './util'

View File

@@ -1,2 +1,2 @@
export * from "./setupInit"
export * from "./setupUninit"
export * from './setupInit'
export * from './setupUninit'

View File

@@ -1,8 +1,8 @@
import { VersionRange } from "../../../base/lib/exver"
import * as T from "../../../base/lib/types"
import { once } from "../util"
import { VersionRange } from '../../../base/lib/exver'
import * as T from '../../../base/lib/types'
import { once } from '../util'
export type InitKind = "install" | "update" | "restore" | null
export type InitKind = 'install' | 'update' | 'restore' | null
export type InitFn<Kind extends InitKind = InitKind> = (
effects: T.Effects,
@@ -31,7 +31,7 @@ export function setupInit(...inits: InitScriptOrFn[]): T.ExpectedExports.init {
complete.then(() => fn()).catch(console.error),
)
try {
if ("init" in init) await init.init(e, opts.kind)
if ('init' in init) await init.init(e, opts.kind)
else await init(e, opts.kind)
} finally {
res()
@@ -43,7 +43,7 @@ export function setupInit(...inits: InitScriptOrFn[]): T.ExpectedExports.init {
}
export function setupOnInit(onInit: InitScriptOrFn): InitScript {
return "init" in onInit
return 'init' in onInit
? onInit
: {
init: async (effects, kind) => {

View File

@@ -1,5 +1,5 @@
import { ExtendedVersion, VersionRange } from "../../../base/lib/exver"
import * as T from "../../../base/lib/types"
import { ExtendedVersion, VersionRange } from '../../../base/lib/exver'
import * as T from '../../../base/lib/types'
export type UninitFn = (
effects: T.Effects,
@@ -34,14 +34,14 @@ export function setupUninit(
): T.ExpectedExports.uninit {
return async (opts) => {
for (const uninit of uninits) {
if ("uninit" in uninit) await uninit.uninit(opts.effects, opts.target)
if ('uninit' in uninit) await uninit.uninit(opts.effects, opts.target)
else await uninit(opts.effects, opts.target)
}
}
}
export function setupOnUninit(onUninit: UninitScriptOrFn): UninitScript {
return "uninit" in onUninit
return 'uninit' in onUninit
? onUninit
: {
uninit: async (effects, target) => {

View File

@@ -1,10 +1,10 @@
import { object, string } from "ts-matches"
import { Effects } from "../Effects"
import { Origin } from "./Origin"
import { AddSslOptions, BindParams } from "../osBindings"
import { Security } from "../osBindings"
import { BindOptions } from "../osBindings"
import { AlpnInfo } from "../osBindings"
import { object, string } from 'ts-matches'
import { Effects } from '../Effects'
import { Origin } from './Origin'
import { AddSslOptions, BindParams } from '../osBindings'
import { Security } from '../osBindings'
import { BindOptions } from '../osBindings'
import { AlpnInfo } from '../osBindings'
export { AddSslOptions, Security, BindOptions }
@@ -12,8 +12,8 @@ export const knownProtocols = {
http: {
secure: null,
defaultPort: 80,
withSsl: "https",
alpn: { specified: ["http/1.1"] } as AlpnInfo,
withSsl: 'https',
alpn: { specified: ['http/1.1'] } as AlpnInfo,
},
https: {
secure: { ssl: true },
@@ -22,8 +22,8 @@ export const knownProtocols = {
ws: {
secure: null,
defaultPort: 80,
withSsl: "wss",
alpn: { specified: ["http/1.1"] } as AlpnInfo,
withSsl: 'wss',
alpn: { specified: ['http/1.1'] } as AlpnInfo,
},
wss: {
secure: { ssl: true },
@@ -140,8 +140,8 @@ export class MultiHost {
addXForwardedHeaders: false,
preferredExternalPort: knownProtocols[sslProto].defaultPort,
scheme: sslProto,
alpn: "alpn" in protoInfo ? protoInfo.alpn : null,
...("addSsl" in options ? options.addSsl : null),
alpn: 'alpn' in protoInfo ? protoInfo.alpn : null,
...('addSsl' in options ? options.addSsl : null),
}
: options.addSsl
? {
@@ -149,7 +149,7 @@ export class MultiHost {
preferredExternalPort: 443,
scheme: sslProto,
alpn: null,
...("addSsl" in options ? options.addSsl : null),
...('addSsl' in options ? options.addSsl : null),
}
: null
@@ -169,8 +169,8 @@ export class MultiHost {
private getSslProto(options: BindOptionsByKnownProtocol) {
const proto = options.protocol
const protoInfo = knownProtocols[proto]
if (inObject("noAddSsl", options) && options.noAddSsl) return null
if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
if (inObject('noAddSsl', options) && options.noAddSsl) return null
if ('withSsl' in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
if (protoInfo.secure?.ssl) return proto
return null
}

View File

@@ -1,7 +1,7 @@
import { AddressInfo } from "../types"
import { AddressReceipt } from "./AddressReceipt"
import { MultiHost, Scheme } from "./Host"
import { ServiceInterfaceBuilder } from "./ServiceInterfaceBuilder"
import { AddressInfo } from '../types'
import { AddressReceipt } from './AddressReceipt'
import { MultiHost, Scheme } from './Host'
import { ServiceInterfaceBuilder } from './ServiceInterfaceBuilder'
export class Origin {
constructor(
@@ -21,9 +21,9 @@ export class Origin {
.map(
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
)
.join("&")
.join('&')
const qp = qpEntries.length ? `?${qpEntries}` : ""
const qp = qpEntries.length ? `?${qpEntries}` : ''
return {
hostId: this.host.options.id,

View File

@@ -1,6 +1,6 @@
import { ServiceInterfaceType } from "../types"
import { Effects } from "../Effects"
import { Scheme } from "./Host"
import { ServiceInterfaceType } from '../types'
import { Effects } from '../Effects'
import { Scheme } from './Host'
/**
* A helper class for creating a Network Interface

View File

@@ -1,6 +1,6 @@
import * as T from "../types"
import { once } from "../util"
import { AddressReceipt } from "./AddressReceipt"
import * as T from '../types'
import { once } from '../util'
import { AddressReceipt } from './AddressReceipt'
declare const UpdateServiceInterfacesProof: unique symbol
export type UpdateServiceInterfacesReceipt = {

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
import type { AnyVerifyingKey } from './AnyVerifyingKey'
export type AcceptSigners =
| { signer: AnyVerifyingKey }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
import type { Guid } from './Guid'
export type ActionInput = {
eventId: Guid

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionVisibility } from "./ActionVisibility"
import type { AllowedStatuses } from "./AllowedStatuses"
import type { ActionVisibility } from './ActionVisibility'
import type { AllowedStatuses } from './AllowedStatuses'
export type ActionMetadata = {
/**

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionResultV0 } from "./ActionResultV0"
import type { ActionResultV1 } from "./ActionResultV1"
import type { ActionResultV0 } from './ActionResultV0'
import type { ActionResultV1 } from './ActionResultV1'
export type ActionResult =
| ({ version: "0" } & ActionResultV0)
| ({ version: "1" } & ActionResultV1)
| ({ version: '0' } & ActionResultV0)
| ({ version: '1' } & ActionResultV1)

View File

@@ -11,7 +11,7 @@ export type ActionResultMember = {
description: string | null
} & (
| {
type: "single"
type: 'single'
/**
* The actual string value to display
*/
@@ -30,7 +30,7 @@ export type ActionResultMember = {
masked: boolean
}
| {
type: "group"
type: 'group'
/**
* An new group of nested values, experienced by the user as an accordion dropdown
*/

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionResultValue } from "./ActionResultValue"
import type { ActionResultValue } from './ActionResultValue'
export type ActionResultV1 = {
/**

View File

@@ -1,9 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionResultMember } from "./ActionResultMember"
import type { ActionResultMember } from './ActionResultMember'
export type ActionResultValue =
| {
type: "single"
type: 'single'
/**
* The actual string value to display
*/
@@ -22,7 +22,7 @@ export type ActionResultValue =
masked: boolean
}
| {
type: "group"
type: 'group'
/**
* An new group of nested values, experienced by the user as an accordion dropdown
*/

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ActionVisibility = "hidden" | { disabled: string } | "enabled"
export type ActionVisibility = 'hidden' | { disabled: string } | 'enabled'

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
import type { Guid } from './Guid'
export type AddAdminParams = { signer: Guid }

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AnySignature } from "./AnySignature"
import type { Blake3Commitment } from "./Blake3Commitment"
import type { AnySignature } from './AnySignature'
import type { Blake3Commitment } from './Blake3Commitment'
export type AddAssetParams = {
version: string

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { LocaleString } from "./LocaleString"
import type { LocaleString } from './LocaleString'
export type AddCategoryParams = { id: string; name: LocaleString }

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AnySignature } from "./AnySignature"
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
import type { AnySignature } from './AnySignature'
import type { MerkleArchiveCommitment } from './MerkleArchiveCommitment'
export type AddMirrorParams = {
url: string

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AnySignature } from "./AnySignature"
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
import type { AnySignature } from './AnySignature'
import type { MerkleArchiveCommitment } from './MerkleArchiveCommitment'
export type AddPackageParams = {
urls: string[]

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
import type { PackageId } from "./PackageId"
import type { Guid } from './Guid'
import type { PackageId } from './PackageId'
export type AddPackageSignerParams = {
id: PackageId

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from "./PackageId"
import type { PackageId } from './PackageId'
export type AddPackageToCategoryParams = { id: string; package: PackageId }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AlpnInfo } from "./AlpnInfo"
import type { AlpnInfo } from './AlpnInfo'
export type AddSslOptions = {
preferredExternalPort: number

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HostId } from "./HostId"
import type { HostId } from './HostId'
export type AddressInfo = {
username: string | null

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { LocaleString } from "./LocaleString"
import type { LocaleString } from './LocaleString'
export type Alerts = {
install: LocaleString | null

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Algorithm = "ecdsa" | "ed25519"
export type Algorithm = 'ecdsa' | 'ed25519'

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageDataEntry } from "./PackageDataEntry"
import type { PackageId } from "./PackageId"
import type { PackageDataEntry } from './PackageDataEntry'
import type { PackageId } from './PackageId'
export type AllPackageData = { [key: PackageId]: PackageDataEntry }

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type AllowedStatuses = "only-running" | "only-stopped" | "any"
export type AllowedStatuses = 'only-running' | 'only-stopped' | 'any'

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { MaybeUtf8String } from "./MaybeUtf8String"
import type { MaybeUtf8String } from './MaybeUtf8String'
export type AlpnInfo = "reflect" | { specified: Array<MaybeUtf8String> }
export type AlpnInfo = 'reflect' | { specified: Array<MaybeUtf8String> }

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ApiState = "error" | "initializing" | "running"
export type ApiState = 'error' | 'initializing' | 'running'

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EncryptedWire } from "./EncryptedWire"
import type { EncryptedWire } from './EncryptedWire'
export type AttachParams = {
password: EncryptedWire | null

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BlockDev } from "./BlockDev"
import type { Cifs } from "./Cifs"
import type { BlockDev } from './BlockDev'
import type { Cifs } from './Cifs'
export type BackupTargetFS =
| ({ type: "disk" } & BlockDev)
| ({ type: "cifs" } & Cifs)
| ({ type: 'disk' } & BlockDev)
| ({ type: 'cifs' } & Cifs)

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HostId } from "./HostId"
import type { HostId } from './HostId'
export type BindId = { id: HostId; internalPort: number }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BindOptions } from "./BindOptions"
import type { NetInfo } from "./NetInfo"
import type { BindOptions } from './BindOptions'
import type { NetInfo } from './NetInfo'
export type BindInfo = { enabled: boolean; options: BindOptions; net: NetInfo }

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AddSslOptions } from "./AddSslOptions"
import type { Security } from "./Security"
import type { AddSslOptions } from './AddSslOptions'
import type { Security } from './Security'
export type BindOptions = {
preferredExternalPort: number

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AddSslOptions } from "./AddSslOptions"
import type { HostId } from "./HostId"
import type { Security } from "./Security"
import type { AddSslOptions } from './AddSslOptions'
import type { HostId } from './HostId'
import type { Security } from './Security'
export type BindParams = {
id: HostId

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
import type { GatewayId } from './GatewayId'
export type BindingGatewaySetEnabledParams = {
internalPort: number

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Base64 } from "./Base64"
import type { Base64 } from './Base64'
export type Blake3Commitment = { hash: Base64; size: number }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { LocaleString } from "./LocaleString"
import type { LocaleString } from './LocaleString'
export type Category = { name: LocaleString }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from "./PackageId"
import type { PackageId } from './PackageId'
export type CheckDependenciesParam = { packageIds?: Array<PackageId> }

View File

@@ -1,10 +1,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HealthCheckId } from "./HealthCheckId"
import type { NamedHealthCheckResult } from "./NamedHealthCheckResult"
import type { PackageId } from "./PackageId"
import type { ReplayId } from "./ReplayId"
import type { TaskEntry } from "./TaskEntry"
import type { Version } from "./Version"
import type { HealthCheckId } from './HealthCheckId'
import type { NamedHealthCheckResult } from './NamedHealthCheckResult'
import type { PackageId } from './PackageId'
import type { ReplayId } from './ReplayId'
import type { TaskEntry } from './TaskEntry'
import type { Version } from './Version'
export type CheckDependenciesResult = {
packageId: PackageId

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionId } from "./ActionId"
import type { ActionId } from './ActionId'
export type ClearActionsParams = { except: Array<ActionId> }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BindId } from "./BindId"
import type { BindId } from './BindId'
export type ClearBindingsParams = { except: Array<BindId> }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
import type { ServiceInterfaceId } from './ServiceInterfaceId'
export type ClearServiceInterfacesParams = { except: Array<ServiceInterfaceId> }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ImageId } from "./ImageId"
import type { ImageId } from './ImageId'
export type CreateSubcontainerFsParams = {
imageId: ImageId

View File

@@ -1,10 +1,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionId } from "./ActionId"
import type { PackageId } from "./PackageId"
import type { ReplayId } from "./ReplayId"
import type { TaskInput } from "./TaskInput"
import type { TaskSeverity } from "./TaskSeverity"
import type { TaskTrigger } from "./TaskTrigger"
import type { ActionId } from './ActionId'
import type { PackageId } from './PackageId'
import type { ReplayId } from './ReplayId'
import type { TaskInput } from './TaskInput'
import type { TaskSeverity } from './TaskSeverity'
import type { TaskTrigger } from './TaskTrigger'
export type CreateTaskParams = {
replayId: ReplayId

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CurrentDependencyInfo } from "./CurrentDependencyInfo"
import type { PackageId } from "./PackageId"
import type { CurrentDependencyInfo } from './CurrentDependencyInfo'
import type { PackageId } from './PackageId'
export type CurrentDependencies = { [key: PackageId]: CurrentDependencyInfo }

View File

@@ -1,9 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DataUrl } from "./DataUrl"
import type { LocaleString } from "./LocaleString"
import type { DataUrl } from './DataUrl'
import type { LocaleString } from './LocaleString'
export type CurrentDependencyInfo = {
title: LocaleString | null
icon: DataUrl | null
versionRange: string
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
} & ({ kind: 'exists' } | { kind: 'running'; healthChecks: string[] })

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { MetadataSrc } from "./MetadataSrc"
import type { MetadataSrc } from './MetadataSrc'
export type DepInfo = {
description: string | null

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DepInfo } from "./DepInfo"
import type { PackageId } from "./PackageId"
import type { DepInfo } from './DepInfo'
import type { PackageId } from './PackageId'
export type Dependencies = { [key: PackageId]: DepInfo }

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DataUrl } from "./DataUrl"
import type { LocaleString } from "./LocaleString"
import type { DataUrl } from './DataUrl'
import type { LocaleString } from './LocaleString'
export type DependencyMetadata = {
title: LocaleString | null

View File

@@ -1,12 +1,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HealthCheckId } from "./HealthCheckId"
import type { PackageId } from "./PackageId"
import type { HealthCheckId } from './HealthCheckId'
import type { PackageId } from './PackageId'
export type DependencyRequirement =
| {
kind: "running"
kind: 'running'
id: PackageId
healthChecks: Array<HealthCheckId>
versionRange: string
}
| { kind: "exists"; id: PackageId; versionRange: string }
| { kind: 'exists'; id: PackageId; versionRange: string }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { LocaleString } from "./LocaleString"
import type { LocaleString } from './LocaleString'
export type Description = { short: LocaleString; long: LocaleString }

View File

@@ -1,8 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { StartStop } from "./StartStop"
import type { StartStop } from './StartStop'
export type DesiredStatus =
| { main: "stopped" }
| { main: "restarting" }
| { main: "running" }
| { main: "backing-up"; onComplete: StartStop }
| { main: 'stopped' }
| { main: 'restarting' }
| { main: 'running' }
| { main: 'backing-up'; onComplete: StartStop }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
import type { Guid } from './Guid'
export type DestroySubcontainerFsParams = { guid: Guid }

View File

@@ -2,7 +2,7 @@
export type DeviceFilter = {
description: string
class: "processor" | "display"
class: 'processor' | 'display'
product: string | null
vendor: string | null
capabilities?: Array<string>

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
import type { GatewayId } from './GatewayId'
export type DomainSettings = { gateway: GatewayId }

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
import type { ContactInfo } from "./ContactInfo"
import type { Guid } from "./Guid"
import type { AnyVerifyingKey } from './AnyVerifyingKey'
import type { ContactInfo } from './ContactInfo'
import type { Guid } from './Guid'
export type EditSignerParams = {
id: Guid

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
import type { Guid } from './Guid'
export type EventId = { eventId: Guid }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionId } from "./ActionId"
import type { ActionMetadata } from "./ActionMetadata"
import type { ActionId } from './ActionId'
import type { ActionMetadata } from './ActionMetadata'
export type ExportActionParams = { id: ActionId; metadata: ActionMetadata }

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AddressInfo } from "./AddressInfo"
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
import type { ServiceInterfaceType } from "./ServiceInterfaceType"
import type { AddressInfo } from './AddressInfo'
import type { ServiceInterfaceId } from './ServiceInterfaceId'
import type { ServiceInterfaceType } from './ServiceInterfaceType'
export type ExportServiceInterfaceParams = {
id: ServiceInterfaceId

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FileType = "file" | "directory" | "infer"
export type FileType = 'file' | 'directory' | 'infer'

View File

@@ -1,9 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DataUrl } from "./DataUrl"
import type { Guid } from "./Guid"
import type { OsIndex } from "./OsIndex"
import type { PackageIndex } from "./PackageIndex"
import type { SignerInfo } from "./SignerInfo"
import type { DataUrl } from './DataUrl'
import type { Guid } from './Guid'
import type { OsIndex } from './OsIndex'
import type { PackageIndex } from './PackageIndex'
import type { SignerInfo } from './SignerInfo'
export type FullIndex = {
name: string | null

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { NamedProgress } from "./NamedProgress"
import type { Progress } from "./Progress"
import type { NamedProgress } from './NamedProgress'
import type { Progress } from './Progress'
export type FullProgress = { overall: Progress; phases: Array<NamedProgress> }

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
import type { GatewayId } from './GatewayId'
export type GatewayInfo = { id: GatewayId; name: string; public: boolean }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionId } from "./ActionId"
import type { PackageId } from "./PackageId"
import type { ActionId } from './ActionId'
import type { PackageId } from './PackageId'
export type GetActionInputParams = { packageId?: PackageId; actionId: ActionId }

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CallbackId } from "./CallbackId"
import type { PackageId } from "./PackageId"
import type { CallbackId } from './CallbackId'
import type { PackageId } from './PackageId'
export type GetContainerIpParams = {
packageId?: PackageId

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CallbackId } from "./CallbackId"
import type { HostId } from "./HostId"
import type { PackageId } from "./PackageId"
import type { CallbackId } from './CallbackId'
import type { HostId } from './HostId'
import type { PackageId } from './PackageId'
export type GetHostInfoParams = {
hostId: HostId

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageDetailLevel } from "./PackageDetailLevel"
import type { PackageId } from "./PackageId"
import type { Version } from "./Version"
import type { PackageDetailLevel } from './PackageDetailLevel'
import type { PackageId } from './PackageId'
import type { Version } from './Version'
export type GetPackageParams = {
id: PackageId | null

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageInfoShort } from "./PackageInfoShort"
import type { PackageVersionInfo } from "./PackageVersionInfo"
import type { Version } from "./Version"
import type { PackageInfoShort } from './PackageInfoShort'
import type { PackageVersionInfo } from './PackageVersionInfo'
import type { Version } from './Version'
export type GetPackageResponse = {
categories: string[]

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageVersionInfo } from "./PackageVersionInfo"
import type { Version } from "./Version"
import type { PackageVersionInfo } from './PackageVersionInfo'
import type { Version } from './Version'
export type GetPackageResponseFull = {
categories: string[]

Some files were not shown because too many files have changed in this diff Show More