From de28b30eea95783b275c41a69724069a3310dbb5 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 21 Aug 2026 08:44:07 -0400 Subject: [PATCH] Initial StartOS package for Station Master (v0.5.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Built from source via a git submodule pinned to a tag, not a published image — the Dockerfile, main.ts, and manifest should never need to change for an ordinary version bump, only the submodule pin (see UPDATING.md). One volume, one interface serving the browser client + lobby/intent API + SSE stream from a single origin, no dependencies. The server-wide join secret (D14) is seeded on install, exposed via the Get Join Secret action, and blocks start behind a critical task until retrieved — the same first-set/rotation pattern as actual-budget-startos's admin password. Verified on phoenix.local: installs, the critical task correctly blocks an ordinary start, force-starting confirms the daemon binds its port and serves the client, and store.json correctly holds the install-seeded join secret. Not verified: the Get Join Secret action's execution end-to-end — start-cli's `package action run` fails with a client-side deserialization error that reproduces identically against actual-budget's already-shipped equivalent action, so this looks like a start-cli issue rather than a defect here. icon.svg is still the scaffold's hello-world placeholder — no real Station Master icon exists yet to ship in its place. --- .dockerignore | 6 + .github/workflows/build.yml | 17 + .github/workflows/release.yml | 19 + .github/workflows/syncNext.yml | 24 + .github/workflows/tagAndRelease.yml | 24 + .gitignore | 8 + .gitmodules | 3 + AGENTS.md | 49 + CLAUDE.md | 1 + Dockerfile | 23 + LICENSE | 7 + Makefile | 3 + README.md | 160 ++ UPDATING.md | 41 + assets/.gitkeep | 0 icon.svg | 11 + instructions.md | 38 + package-lock.json | 1670 +++++++++++++++++++++ package.json | 26 + startos/actions/getJoinSecret.ts | 51 + startos/actions/index.ts | 4 + startos/backups.ts | 8 + startos/dependencies.ts | 5 + startos/fileModels/store.json.ts | 14 + startos/i18n/dictionaries/default.ts | 24 + startos/i18n/dictionaries/translations.ts | 48 + startos/i18n/index.ts | 8 + startos/index.ts | 11 + startos/init/generateJoinSecret.ts | 26 + startos/init/index.ts | 18 + startos/interfaces.ts | 28 + startos/main.ts | 50 + startos/manifest/i18n.ts | 41 + startos/manifest/index.ts | 29 + startos/sdk.ts | 9 + startos/utils.ts | 8 + startos/versions/current.ts | 16 + startos/versions/index.ts | 7 + station-master | 1 + tsconfig.json | 4 + 40 files changed, 2540 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/syncNext.yml create mode 100644 .github/workflows/tagAndRelease.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 UPDATING.md create mode 100644 assets/.gitkeep create mode 100644 icon.svg create mode 100644 instructions.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 startos/actions/getJoinSecret.ts create mode 100644 startos/actions/index.ts create mode 100644 startos/backups.ts create mode 100644 startos/dependencies.ts create mode 100644 startos/fileModels/store.json.ts create mode 100644 startos/i18n/dictionaries/default.ts create mode 100644 startos/i18n/dictionaries/translations.ts create mode 100644 startos/i18n/index.ts create mode 100644 startos/index.ts create mode 100644 startos/init/generateJoinSecret.ts create mode 100644 startos/init/index.ts create mode 100644 startos/interfaces.ts create mode 100644 startos/main.ts create mode 100644 startos/manifest/i18n.ts create mode 100644 startos/manifest/index.ts create mode 100644 startos/sdk.ts create mode 100644 startos/utils.ts create mode 100644 startos/versions/current.ts create mode 100644 startos/versions/index.ts create mode 160000 station-master create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f4213aa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.gitmodules +node_modules +*.s9pk +docker-images +javascript \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8111d07 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: Build + +on: + workflow_dispatch: + pull_request: + paths-ignore: ['*.md'] + branches: ['master'] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.event.pull_request.draft == false + uses: Start9Labs/start-technologies/.github/workflows/build.yml@master + # No DEV_KEY — a PR build doesn't publish, so it doesn't need the signing key. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6dcef4a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: Release + +on: + push: + tags: + - 'v*.*' + +jobs: + release: + uses: Start9Labs/start-technologies/.github/workflows/release.yml@master + with: + RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }} + S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }} + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + permissions: + contents: write diff --git a/.github/workflows/syncNext.yml b/.github/workflows/syncNext.yml new file mode 100644 index 0000000..14a28b8 --- /dev/null +++ b/.github/workflows/syncNext.yml @@ -0,0 +1,24 @@ +name: Sync next + +# Carries every change that lands on the base branch onto the paired `next` +# iteration branch, so `next` never drifts behind what has already shipped. +# `next` is created on the first run if the repo does not have one yet. +# +# List every base branch this package maintains. Most packages have one; the +# multi-branch packages list each major line or flavor (e.g. 28.x, 29.x). A +# package whose default branch is `main` must say `main` here — a workflow +# pointed at a branch the repo does not use never runs. +# +# No paths-ignore: a docs-only commit on the base still has to reach `next`, +# or the next merge back re-introduces the stale copy. +on: + push: + branches: ['master'] + workflow_dispatch: + +jobs: + sync: + uses: Start9Labs/start-technologies/.github/workflows/syncNext.yml@master + permissions: + contents: write + pull-requests: write diff --git a/.github/workflows/tagAndRelease.yml b/.github/workflows/tagAndRelease.yml new file mode 100644 index 0000000..f000851 --- /dev/null +++ b/.github/workflows/tagAndRelease.yml @@ -0,0 +1,24 @@ +name: Tag and Release + +on: + push: + branches: ['master'] + paths-ignore: ['*.md'] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tag: + uses: Start9Labs/start-technologies/.github/workflows/tagAndRelease.yml@master + with: + REFERENCE_REGISTRY: ${{ vars.REFERENCE_REGISTRY }} + RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }} + S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }} + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + permissions: + contents: write diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4dad48c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.s9pk +startos/*.js +node_modules/ +.DS_Store +.vscode/ +docker-images +javascript +ncc-cache \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7cd3021 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "station-master"] + path = station-master + url = https://draco.local:53871/Jesse.Markowitz/station-master.git diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a87a02 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,49 @@ +# AGENTS.md + +This is a StartOS service-package repository — it builds a `.s9pk` for StartOS. + +Develop it inside a StartOS packaging workspace created by `start-cli s9pk init-workspace`, +which provides the packaging guide and agent context one level up. If you're reading this in a +bare clone with no workspace, the full guide is at . + +**Start every task at the recipe index** — `../start-technologies/projects/start-sdk/docs/src/recipes.md` +(or ). It maps an intent ("prompt the user to create +admin credentials", "expose a web UI") to the constructs, the reference pages, and a named production +package to copy. Find the recipe before you read this package's neighbours: a package you reach by +grepping may be non-conformant, and the recipe outranks it. + +Freshly scaffolded? Work the +[New Package Checklist](../start-technologies/projects/start-sdk/docs/src/new-package-checklist.md) +(or ) from top to bottom. It is a +guide page, not a file in this repo — read it, don't copy it in. + +Keep `README.md` (technical reference for an AI support or administering agent) and +`instructions.md` (end-user docs) in sync with your changes. + +**Bugs and feature requests are issues on this repo** (self-hosted Gitea, not GitHub) — file +them as you find them. Don't record work in the repo instead: no `TODO.md`, no `NOTES.md`, no +`PLAN.md`. What you verified, tried, and decided belongs in the commit message and the PR body. + +## This repo + +- **Station Master is a git submodule (`station-master/`), not vendored source.** It tracks + [Jesse's own game repo](https://draco.local:53871/Jesse.Markowitz/station-master), self-hosted, + pinned to a tag. See `UPDATING.md` for how the pin is bumped — the `Dockerfile`, `main.ts`, and + the manifest should never need to change for an ordinary version bump. +- **Built from source, not a published image.** The `Dockerfile` is entirely this repo's own: + a `node:*-slim` builder stage runs the submodule's `npm run build:web` to produce the static + client, then a runtime stage copies only `package.json`, `src/`, and the built `dist/` — no + `node_modules`, since `src/server/index.ts` has zero runtime dependencies (see the submodule's + own `TODO.md` for why) and Node's native TypeScript type-stripping means it needs no compile + step either. +- **The join secret (D14 in the game's `docs/architecture/multiplayer.md`) is this package's one + piece of state beyond the game data itself.** `startos/fileModels/store.json.ts` holds it, + seeded on install (`startos/init/generateJoinSecret.ts`) since the server refuses to start + without one, and rotated by the **Get Join Secret** action + (`startos/actions/getJoinSecret.ts`) — the same action handles first retrieval and rotation, per + `recipe-admin-credentials.md`. +- **One volume, one interface, no dependencies.** Everything the server persists — + `store.json` and every game's own save data — lives on the `data` volume; the single `ui` + interface carries the browser client, the lobby/intent HTTP API, and the SSE game stream from + one port, which is what keeps players reachable from different addresses in the same game + (`docs/architecture/deployment.md` §3 in the game repo). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..380823a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# Station Master's multiplayer server, built from source — there is no published image to wrap. +# Source is the `station-master` git submodule, pinned to a tag; see UPDATING.md for how that pin +# is bumped. This Dockerfile never needs to change for a version bump, only the submodule ref. + +# --- builder: produces the static browser client (dist/) --- +# The server itself has zero runtime dependencies (station-master/TODO.md), but `npm run +# build:web` shells out to `tsc`, which lives in devDependencies — hence the separate stage. +FROM node:22.23.1-slim AS builder +WORKDIR /build +COPY station-master/package.json station-master/package-lock.json ./ +RUN npm ci +COPY station-master/ ./ +RUN npm run build:web + +# --- runtime: the server, no node_modules needed at all --- +FROM node:22.23.1-slim +WORKDIR /app +# Node >=22.18 strips TypeScript types natively (station-master/package.json's engines field), +# so the server runs straight from source with no build/compile step. +COPY station-master/package.json ./ +COPY station-master/src/ ./src/ +COPY --from=builder /build/dist/ ./dist/ +CMD ["node", "src/server/index.ts"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..450e580 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2026 Jesse Markowitz + +All rights reserved. + +This software is proprietary. No license is granted to copy, modify, +distribute, or use this software, in whole or in part, without the express +prior written permission of the copyright holder. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8315da3 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +ARCHES := x86 arm +# overrides to s9pk.mk must precede the include statement +include node_modules/@start9labs/start-sdk/s9pk.mk diff --git a/README.md b/README.md new file mode 100644 index 0000000..f27ae3f --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +

+ Station Master Logo +

+ +# Station Master on StartOS + +> Everything not listed in this document should behave the same as upstream +> Station Master. If a feature, setting, or behavior is not mentioned here, +> the upstream documentation is accurate and fully applicable — see the +> Documentation section of `instructions.md` for links. + +Station Master is a railroad operations board game with an authoritative multiplayer server; +solitaire needs no server and is not what this package is for. This package runs that server — +the browser client, the lobby, and the intent/SSE API — as a single StartOS service. + +--- + +## Table of Contents + +- [Image and Container Runtime](#image-and-container-runtime) +- [Volume and Data Layout](#volume-and-data-layout) +- [File Models](#file-models) +- [Dependencies](#dependencies) +- [Network Access and Interfaces](#network-access-and-interfaces) +- [Installation and First-Run Flow](#installation-and-first-run-flow) +- [Actions](#actions) +- [Tasks](#tasks) +- [Health Checks](#health-checks) +- [Backups and Restore](#backups-and-restore) +- [Limitations and Differences](#limitations-and-differences) +- [Quick Reference for AI Consumers](#quick-reference-for-ai-consumers) + +--- + +## Image and Container Runtime + +Built from source with a custom `Dockerfile` — there is no published Station Master image. + +| What to Document | Value | +| --- | --- | +| Image source | Custom multi-stage `Dockerfile`: `node:*-slim` builder runs `npm run build:web` to produce the static client, then a second `node:*-slim` stage copies only `package.json`, `src/`, and the built `dist/` — no `node_modules` in the runtime stage, since the server has zero runtime dependencies | +| Architectures | x86_64, aarch64 | +| Entrypoint | `node src/server/index.ts` — runs straight from TypeScript source; no compile step, see Limitations | + +One subcontainer: `station-master-sub`, running the single daemon `server`. + +## Volume and Data Layout + +One volume, `data`, mounted at `/data` (`DATA_DIR`). + +| What to Document | Value | +| --- | --- | +| Volume names | `data` | +| Mount points | `/data` | +| StartOS files | `store.json` — holds the join secret (see File Models) | +| Database | None — flat files. Each game is `games//game.json` (`{ engineVersion, seed, config, playerNames, history, status, createdAt }`) plus `turn-timings.json`, an `index.json` naming every game, and lobby state for games not yet started | + +## File Models + +One StartOS-managed file, `store.json`, on the `data` volume. + +- **`store.json`** — JSON, holds `joinSecret` (a string). Seeded on install with a random + 24-character value (`init/generateJoinSecret.ts`) — the daemon will not start without one, so + it is never left unset. Rewritten only by the **Get Join Secret** action, which generates a new + value on every run; the value is otherwise never touched, so a hand edit to it survives until + the action is next run. `main.ts` reads it reactively, so a rewrite restarts the daemon with the + new value automatically. + +Everything else under `/data` — `games/`, `index.json`, lobby and session state — is the +application's own persistence, written directly by the server process, not by a StartOS file +model. + +## Dependencies + +None. + +## Network Access and Interfaces + +One interface, `ui`, on the single port the daemon listens on. It serves the browser client, the +lobby and intent HTTP API, and the per-seat SSE game stream — all from the same origin, which is +what lets a player reach the server from a LAN address and another player reach it from a +different one in the same game. + +## Installation and First-Run Flow + +No setup wizard. On install, a join secret is generated and stored immediately (see File Models), +and a critical task is raised pointing at **Get Join Secret** — the service starts and is usable +the moment the daemon is healthy, but a player cannot create or join a game until the join secret +has been retrieved and shared with them. + +## Actions + +- **Get Join Secret** (`get-join-secret`) — run this any time you want to read the current join + secret, or to invalidate it and issue a new one. Every run **generates a fresh secret, + overwrites the stored one, and restarts the daemon** with it — there is no read-only mode. + Rotating does not disconnect players already seated in a running game (D14's join secret gates + the lobby door, not an in-progress session); it only invalidates the old value for anyone who + has not yet joined or created a game. Completes in a few seconds, safe to repeat. + +## Tasks + +- **Get the join secret to share with players** — raised on install, severity `critical`. Points + at the **Get Join Secret** action. Clears the moment that action is run for the first time; it + does not return afterward (running it again to rotate does not re-raise it). + +## Health Checks + +- **Multiplayer Server** — `checkPortListening` against the daemon's port. "Not ready" means the + process has not yet bound its port; on a normal start this clears within a second or two; on a + cold container start it briefly reflects the daemon starting up. + +## Backups and Restore + +Strategy: the entire `data` volume is backed up wholesale (`ofVolumes`) — every file copied +exactly as it sits on disk, nothing dumped and replayed. + +That includes every game's full intent history, so a restore can resume any in-progress game +exactly where it left off, and the join secret in `store.json`, so previously shared join links +keep working after a restore. Nothing needs to be rebuilt or re-entered after a restore; the +server resumes every saved game on boot the same way it does after an ordinary restart. + +## Limitations and Differences + +1. **No compile step.** The image runs the server directly from TypeScript source using Node's + native type stripping, rather than building a `dist/` for the server the way the browser + client is built. This is upstream's own deployment shape (`docs/architecture/deployment.md` in + the game repo), not something this package changed. +2. **The join secret has no per-player identity.** It is a single server-wide value (D14) that + gates who may create or join a game — it is not a username or password, and StartOS has no + visibility into who a player is once they've joined. +3. **No accounts, and one game at a time per person is expected but not enforced** — a deliberate + upstream design decision (`docs/architecture/multiplayer.md` §11 D13), not a StartOS-specific + limitation. + +--- + +## Quick Reference for AI Consumers + +```yaml +package_id: station-master +image: built from source (Dockerfile) +architectures: [x86_64, aarch64] +subcontainers: [station-master-sub] +volumes: + data: /data +file_models: + - store.json +startos_managed_env_vars: + - DATA_DIR + - JOIN_SECRET +dependencies: none +interfaces: + ui: { type: ui, port: 8081 } +actions: + - get-join-secret +tasks: + - { action: get-join-secret, severity: critical } +health_checks: + - Multiplayer Server +``` diff --git a/UPDATING.md b/UPDATING.md new file mode 100644 index 0000000..6320081 --- /dev/null +++ b/UPDATING.md @@ -0,0 +1,41 @@ +# Updating the upstream version + +Station Master is built from a git submodule at `station-master/` (tracking +[`Jesse.Markowitz/station-master`](https://draco.local:53871/Jesse.Markowitz/station-master), a +self-hosted Gitea repo — this is Jesse's own game, not a third-party upstream, but it is packaged +the same way: `Dockerfile` builds whatever commit the submodule points at, with no pinned +`dockerTag` in the manifest. The submodule pin **is** the version. + +Station Master tags its releases (`v0.5.1`, …) at the commit that bumped `package.json`. Nothing +else about this package should need to change for a bump — not the `Dockerfile`, not `main.ts`, +not the manifest — only the submodule pin and this package's own version (below). + +## Determining the upstream version + +```bash +cd station-master && git fetch --tags && git tag -l --sort=-v:refname | head -5 +``` + +The current pin is the submodule's checked-out commit: + +```bash +git submodule status station-master +``` + +## Applying the bump + +```bash +cd station-master && git fetch --tags && git checkout v +cd .. && git add station-master +``` + +Then bump this package's own version in `startos/versions/current.ts` (see +[Versions](../start-technologies/projects/start-sdk/docs/src/versions.md)) with release notes +naming the Station Master version now bundled — e.g. "Bundles Station Master v0.5.2." — and +rebuild (`make`). + +If the jump carries a multiplayer protocol or persistence-format change (check the game repo's +`CHANGELOG.md` and `docs/architecture/multiplayer.md`), read it before bumping: a running +server's saved games are stamped with `engineVersion` and refuse to resume under a mismatched +one (`src/server/index.ts` in the game repo) — nothing in this package works around that, so an +in-progress game must finish (or be accepted as lost) before an incompatible bump. diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..79990c3 --- /dev/null +++ b/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + Hello! + diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..7bc7f72 --- /dev/null +++ b/instructions.md @@ -0,0 +1,38 @@ +# Station Master + +## Documentation + +- [Rules and design docs](https://draco.local:53871/Jesse.Markowitz/station-master/src/branch/main/docs) + — the rulebook implications, the multiplayer design, and the game's architecture. + +## What you get on StartOS + +This service runs the multiplayer server: a lobby, and a table for 2 to 4 players to play a +competitive or cooperative game together from their own browsers. Everything a player needs — the +game itself, the lobby, and the connection back to this server — is served from the single web +interface below; there is nothing else to install or configure on a player's side. + +## Getting set up + +1. Open this service's **Multiplayer Table** interface. +2. Run the **Get Join Secret** action from this service's Actions menu, and copy the value it + returns. +3. Share that secret, and the address you opened the interface at, with everyone you want to + play with. Anyone holding the secret can create a new game or join one that hasn't started yet. +4. In the browser, click **Multiplayer**, then **Create Game** (or **Join Game** with a game code + someone else created), and enter the join secret when asked. + +## Using Station Master + +### Multiplayer Table + +Create or join a game, seat players (including filling empty seats with a bot), and start. Once a +game is underway, this same interface is where every player plays their turns and watches the +table — there is nothing else to open. + +### Get Join Secret + +Run this whenever you need the current join secret again, or want to shut out anyone still +holding an old one. Every run replaces the secret and immediately puts the new one into effect — +players already seated in a game are unaffected, but the old secret stops working for creating or +joining games from that point on. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0402eae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1670 @@ +{ + "name": "station-master-startos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "station-master-startos", + "dependencies": { + "@start9labs/start-sdk": "2.0.9" + }, + "devDependencies": { + "@types/node": "^22.19.0", + "@vercel/ncc": "^0.38.4", + "prettier": "^3.6.2", + "typescript": "^6.0.3" + } + }, + "node_modules/@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "license": "ISC" + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodable/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-2.0.9.tgz", + "integrity": "sha512-HuSYrS10Bb+f6OyAMlCzd5Qyr+rIauiI0dF8MVh5Ny+eU0gF8/DeFAXlHFgL+9wkSv4UAP5q5yOzlBpXzsNbWQ==", + "bundleDependencies": [ + "@start9labs/start-core", + "eslint", + "typescript-eslint" + ], + "license": "MIT", + "dependencies": { + "@iarna/toml": "^3.0.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@start9labs/start-core": "file:./node_modules/@start9labs/start-core", + "@types/ini": "^4.1.1", + "deep-equality-data-structures": "^2.0.0", + "eslint": "^9.39.4", + "fast-xml-parser": "~5.7.0", + "ini": "^5.0.0", + "isomorphic-fetch": "^3.0.0", + "mime": "^4.1.0", + "typescript-eslint": "^8.61.0", + "yaml": "^2.8.3", + "zod": "4.4.3", + "zod-deep-partial": "^1.2.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-array": { + "version": "0.21.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/core": { + "version": "0.17.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/js": { + "version": "9.39.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/object-schema": { + "version": "2.1.7", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanfs/core": { + "version": "0.19.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanfs/node": { + "version": "0.16.8", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanfs/types": { + "version": "0.15.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@types/estree": { + "version": "1.0.9", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/@types/json-schema": { + "version": "7.0.15", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/type-utils": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/parser": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/project-service": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.0", + "@typescript-eslint/types": "^8.61.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/type-utils": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/types": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.0", + "@typescript-eslint/tsconfig-utils": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/utils": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/acorn": { + "version": "8.16.0", + "inBundle": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/acorn-jsx": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ajv": { + "version": "6.15.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/callsites": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/chalk": { + "version": "4.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/cross-spawn": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/debug": { + "version": "4.4.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/deep-is": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint": { + "version": "9.39.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint-scope": { + "version": "8.4.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/espree": { + "version": "10.4.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/esquery": { + "version": "1.7.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/esrecurse": { + "version": "4.3.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/estraverse": { + "version": "5.3.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/esutils": { + "version": "2.0.3", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/fast-levenshtein": { + "version": "2.0.6", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/fdir": { + "version": "6.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/file-entry-cache": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/flat-cache": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/flatted": { + "version": "3.4.2", + "inBundle": true, + "license": "ISC" + }, + "node_modules/@start9labs/start-sdk/node_modules/globals": { + "version": "14.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/has-flag": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/import-fresh": { + "version": "3.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/is-extglob": { + "version": "2.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/is-glob": { + "version": "4.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/@start9labs/start-sdk/node_modules/json-buffer": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/keyv": { + "version": "4.5.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/levn": { + "version": "0.4.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/lodash.merge": { + "version": "4.6.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/natural-compare": { + "version": "1.4.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/optionator": { + "version": "0.9.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/p-limit": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/parent-module": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/path-exists": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/picomatch": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/prelude-ls": { + "version": "1.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/strip-json-comments": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/supports-color": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/tinyglobby": { + "version": "0.2.17", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ts-api-utils": { + "version": "2.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/type-check": { + "version": "0.4.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/typescript-eslint": { + "version": "8.61.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/uri-js": { + "version": "4.4.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/word-wrap": { + "version": "1.2.5", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/yocto-queue": { + "version": "0.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vercel/ncc": { + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz", + "integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==", + "dev": true, + "license": "MIT", + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/deep-equality-data-structures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-2.0.0.tgz", + "integrity": "sha512-qgrUr7MKXq7VRN+WUpQ48QlXVGL0KdibAoTX8KRg18lgOgqbEKMAW1WZsVCtakY4+XX42pbAJzTz/DlXEFM2Fg==", + "license": "MIT", + "dependencies": { + "object-hash": "^3.0.0" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-deep-partial": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/zod-deep-partial/-/zod-deep-partial-1.4.4.tgz", + "integrity": "sha512-aWkPl7hVStgE01WzbbSxCgX4O+sSpgt8JOjvFUtMTF75VgL6MhWQbiZi+AWGN85SfSTtI9gsOtL1vInoqfDVaA==", + "license": "MIT", + "peerDependencies": { + "zod": "^4.1.13" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..808fb79 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "station-master-startos", + "scripts": { + "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", + "prettier": "prettier --write startos", + "check": "tsc --noEmit" + }, + "dependencies": { + "@start9labs/start-sdk": "2.0.9" + }, + "overrides": { + "@start9labs/start-sdk": "$@start9labs/start-sdk" + }, + "devDependencies": { + "@types/node": "^22.19.0", + "@vercel/ncc": "^0.38.4", + "prettier": "^3.6.2", + "typescript": "^6.0.3" + }, + "prettier": { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true + } +} diff --git a/startos/actions/getJoinSecret.ts b/startos/actions/getJoinSecret.ts new file mode 100644 index 0000000..5ca4f58 --- /dev/null +++ b/startos/actions/getJoinSecret.ts @@ -0,0 +1,51 @@ +import { utils } from '@start9labs/start-sdk' +import { i18n } from '../i18n' +import { sdk } from '../sdk' +import { storeJson } from '../fileModels/store.json' + +/** + * Mints a fresh join secret on every run and returns it — first-set and rotation are the same + * action (recipe-admin-credentials.md). Writing a new value restarts the daemon with it: main.ts + * reads `joinSecret` reactively via `.const(effects)`, so a rotation here takes effect + * immediately rather than needing a manual restart. + * + * Rotating invalidates the old secret for anyone who hasn't joined yet, but never removes a + * player already seated in a game — D14's secret gates the lobby door only (join-secret ≠ + * session token, per `lobby-and-sessions.md` §1). + */ +export const getJoinSecret = sdk.Action.withoutInput( + 'get-join-secret', + + async () => ({ + name: i18n('Get Join Secret'), + description: i18n('Retrieve or rotate the secret players need to create or join a game'), + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + async ({ effects }) => { + const joinSecret = utils.getDefaultString({ charset: 'a-z,A-Z,0-9', len: 24 }) + await storeJson.merge(effects, { joinSecret }) + + return { + version: '1', + title: 'Join Secret', + message: + 'Share this with anyone you want to be able to create or join a game on this server — ' + + "it doesn't identify a person or a seat, just who's allowed at the lobby door. Running " + + 'this action again generates a new one and restarts the server with it; anyone already ' + + 'seated in a game keeps playing, but the old secret stops working for new games and joins.', + result: { + type: 'single', + name: 'Join Secret', + description: null, + value: joinSecret, + masked: true, + copyable: true, + qr: false, + }, + } + }, +) diff --git a/startos/actions/index.ts b/startos/actions/index.ts new file mode 100644 index 0000000..bf57d09 --- /dev/null +++ b/startos/actions/index.ts @@ -0,0 +1,4 @@ +import { sdk } from '../sdk' +import { getJoinSecret } from './getJoinSecret' + +export const actions = sdk.Actions.of().addAction(getJoinSecret) diff --git a/startos/backups.ts b/startos/backups.ts new file mode 100644 index 0000000..07e9c95 --- /dev/null +++ b/startos/backups.ts @@ -0,0 +1,8 @@ +import { sdk } from './sdk' + +// Everything worth keeping is on this one volume: every game's { engineVersion, seed, config, +// history } and turn timings (src/server/persistence.ts in the game repo), plus this package's +// own store.json (the join secret). No database, no second volume — see deployment.md §1. +export const { createBackup, restoreInit } = sdk.setupBackups(async ({ effects }) => + sdk.Backups.ofVolumes('data'), +) diff --git a/startos/dependencies.ts b/startos/dependencies.ts new file mode 100644 index 0000000..7221c4b --- /dev/null +++ b/startos/dependencies.ts @@ -0,0 +1,5 @@ +import { sdk } from './sdk' + +export const setDependencies = sdk.setupDependencies( + async ({ effects }) => ({}), +) diff --git a/startos/fileModels/store.json.ts b/startos/fileModels/store.json.ts new file mode 100644 index 0000000..758f8e4 --- /dev/null +++ b/startos/fileModels/store.json.ts @@ -0,0 +1,14 @@ +import { FileHelper, z } from '@start9labs/start-sdk' +import { sdk } from '../sdk' + +/** + * `joinSecret` is D14's server-wide secret (`docs/architecture/multiplayer.md` in the game repo): + * whoever holds it may create a game, and may join any created game that has not started. The + * server refuses to start without one (`src/server/index.ts`), so `joinSecret` is seeded on + * install (`init/generateJoinSecret.ts`) and never left unset. + */ +const shape = z.object({ + joinSecret: z.string().optional().catch(undefined), +}) + +export const storeJson = FileHelper.json({ base: sdk.volumes.data, subpath: 'store.json' }, shape) diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts new file mode 100644 index 0000000..46cecd8 --- /dev/null +++ b/startos/i18n/dictionaries/default.ts @@ -0,0 +1,24 @@ +export const DEFAULT_LANG = 'en_US' + +const dict = { + // main.ts + 'Starting Station Master!': 0, + 'Multiplayer Server': 1, + 'The multiplayer server is ready': 2, + 'The multiplayer server is not ready': 3, + // interfaces.ts + 'Multiplayer Table': 4, + 'Create or join a game, and play in the browser': 5, + // actions/getJoinSecret.ts + 'Get Join Secret': 6, + 'Retrieve or rotate the secret players need to create or join a game': 7, + // init/generateJoinSecret.ts + 'Get the join secret to share with players': 8, +} as const + +/** + * Plumbing. DO NOT EDIT. + */ +export type I18nKey = keyof typeof dict +export type LangDict = Record<(typeof dict)[I18nKey], string> +export default dict diff --git a/startos/i18n/dictionaries/translations.ts b/startos/i18n/dictionaries/translations.ts new file mode 100644 index 0000000..9a47188 --- /dev/null +++ b/startos/i18n/dictionaries/translations.ts @@ -0,0 +1,48 @@ +import { LangDict } from './default' + +export default { + es_ES: { + 0: '¡Iniciando Station Master!', + 1: 'Servidor multijugador', + 2: 'El servidor multijugador está listo', + 3: 'El servidor multijugador no está listo', + 4: 'Mesa multijugador', + 5: 'Crea o únete a una partida, y juega en el navegador', + 6: 'Obtener el secreto de acceso', + 7: 'Recuperar o rotar el secreto que los jugadores necesitan para crear o unirse a una partida', + 8: 'Obtén el secreto de acceso para compartirlo con los jugadores', + }, + de_DE: { + 0: 'Starte Station Master!', + 1: 'Mehrspieler-Server', + 2: 'Der Mehrspieler-Server ist bereit', + 3: 'Der Mehrspieler-Server ist nicht bereit', + 4: 'Mehrspielertisch', + 5: 'Spiel erstellen oder beitreten und im Browser spielen', + 6: 'Beitrittsgeheimnis abrufen', + 7: 'Das Geheimnis abrufen oder erneuern, das Spieler zum Erstellen oder Beitreten einer Partie benötigen', + 8: 'Hole das Beitrittsgeheimnis, um es mit Spielern zu teilen', + }, + pl_PL: { + 0: 'Uruchamianie Station Master!', + 1: 'Serwer wieloosobowy', + 2: 'Serwer wieloosobowy jest gotowy', + 3: 'Serwer wieloosobowy nie jest gotowy', + 4: 'Stół wieloosobowy', + 5: 'Utwórz lub dołącz do gry i graj w przeglądarce', + 6: 'Pobierz sekret dołączania', + 7: 'Pobierz lub wymień sekret potrzebny graczom do tworzenia gier i dołączania do nich', + 8: 'Pobierz sekret dołączania, aby udostępnić go graczom', + }, + fr_FR: { + 0: 'Démarrage de Station Master !', + 1: 'Serveur multijoueur', + 2: 'Le serveur multijoueur est prêt', + 3: "Le serveur multijoueur n'est pas prêt", + 4: 'Table multijoueur', + 5: 'Créez ou rejoignez une partie, et jouez dans le navigateur', + 6: 'Obtenir le secret de connexion', + 7: 'Récupérer ou renouveler le secret dont les joueurs ont besoin pour créer une partie ou la rejoindre', + 8: 'Obtenez le secret de connexion à partager avec les joueurs', + }, +} satisfies Record diff --git a/startos/i18n/index.ts b/startos/i18n/index.ts new file mode 100644 index 0000000..04cea20 --- /dev/null +++ b/startos/i18n/index.ts @@ -0,0 +1,8 @@ +/** + * Plumbing. DO NOT EDIT this file. + */ +import { setupI18n } from '@start9labs/start-sdk' +import defaultDict, { DEFAULT_LANG } from './dictionaries/default' +import translations from './dictionaries/translations' + +export const i18n = setupI18n(defaultDict, translations, DEFAULT_LANG) diff --git a/startos/index.ts b/startos/index.ts new file mode 100644 index 0000000..7af589b --- /dev/null +++ b/startos/index.ts @@ -0,0 +1,11 @@ +/** + * Plumbing. DO NOT EDIT. + */ +export { createBackup } from './backups' +export { main } from './main' +export { init, uninit } from './init' +export { actions } from './actions' +import { buildManifest } from '@start9labs/start-sdk' +import { manifest as sdkManifest } from './manifest' +import { versionGraph } from './versions' +export const manifest = buildManifest(versionGraph, sdkManifest) diff --git a/startos/init/generateJoinSecret.ts b/startos/init/generateJoinSecret.ts new file mode 100644 index 0000000..7989e2d --- /dev/null +++ b/startos/init/generateJoinSecret.ts @@ -0,0 +1,26 @@ +import { utils } from '@start9labs/start-sdk' +import { getJoinSecret } from '../actions/getJoinSecret' +import { i18n } from '../i18n' +import { sdk } from '../sdk' +import { storeJson } from '../fileModels/store.json' + +/** + * The server exits immediately if `JOIN_SECRET` is unset (`src/server/index.ts` in the game + * repo), so a value has to exist before main.ts's daemon ever starts — seeded here rather than + * left for the user to generate via the action first. + */ +export const seedJoinSecret = sdk.setupOnInit(async (effects, kind) => { + if (kind === 'install') { + await storeJson.merge(effects, { + joinSecret: utils.getDefaultString({ charset: 'a-z,A-Z,0-9', len: 24 }), + }) + await sdk.action.createOwnTask(effects, getJoinSecret, 'critical', { + reason: i18n('Get the join secret to share with players'), + }) + } else { + // 'update' and 'restore' — repairs a corrupted store.json without touching an existing + // joinSecret. A restored volume already carries one (it lives inside the backed-up volume, + // see backups.ts), so there is nothing to seed. + await storeJson.merge(effects, {}) + } +}) diff --git a/startos/init/index.ts b/startos/init/index.ts new file mode 100644 index 0000000..f5f5020 --- /dev/null +++ b/startos/init/index.ts @@ -0,0 +1,18 @@ +import { sdk } from '../sdk' +import { setDependencies } from '../dependencies' +import { setInterfaces } from '../interfaces' +import { versionGraph } from '../versions' +import { actions } from '../actions' +import { restoreInit } from '../backups' +import { seedJoinSecret } from './generateJoinSecret' + +export const init = sdk.setupInit( + restoreInit, + versionGraph, + seedJoinSecret, + setInterfaces, + setDependencies, + actions, +) + +export const uninit = sdk.setupUninit(versionGraph) diff --git a/startos/interfaces.ts b/startos/interfaces.ts new file mode 100644 index 0000000..68af7e2 --- /dev/null +++ b/startos/interfaces.ts @@ -0,0 +1,28 @@ +import { i18n } from './i18n' +import { sdk } from './sdk' +import { uiPort } from './utils' + +// One interface: the server serves the browser client, the lobby/intent HTTP API, and the SSE +// game stream all from this single port (deployment.md's same-origin rule in the game repo — +// baking in a second origin would break relative URLs). type: 'ui' is a label the client is +// meant for a browser; it does not stop the same port from also carrying the API calls that +// client makes back to itself. +export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { + const uiMulti = sdk.MultiHost.of(effects, 'ui-multi') + const uiMultiOrigin = await uiMulti.bindPort(uiPort, { protocol: 'http' }) + const ui = sdk.createInterface(effects, { + name: i18n('Multiplayer Table'), + id: 'ui', + description: i18n('Create or join a game, and play in the browser'), + type: 'ui', + masked: false, + schemeOverride: null, + username: null, + path: '', + query: {}, + }) + + const uiReceipt = await uiMultiOrigin.export([ui]) + + return [uiReceipt] +}) diff --git a/startos/main.ts b/startos/main.ts new file mode 100644 index 0000000..191d89b --- /dev/null +++ b/startos/main.ts @@ -0,0 +1,50 @@ +import { i18n } from './i18n' +import { sdk } from './sdk' +import { uiPort, dataDir } from './utils' +import { storeJson } from './fileModels/store.json' + +export const main = sdk.setupMain(async ({ effects }) => { + console.info(i18n('Starting Station Master!')) + + // Reactive, field-scoped: rotating the join secret (getJoinSecret.ts) rewrites store.json, + // which re-runs setupMain and restarts the daemon with the new value. + const joinSecret = await storeJson.read((s) => s.joinSecret).const(effects) + + return sdk.Daemons.of(effects).addDaemon('server', { + subcontainer: sdk.SubContainer.of( + effects, + { imageId: 'main' }, + sdk.Mounts.of().mountVolume({ + volumeId: 'data', + subpath: null, + mountpoint: dataDir, + readonly: false, + }), + 'station-master-sub', + ), + exec: { + command: ['node', 'src/server/index.ts'], + env: { + // PORT/BIND_ADDRESS/DIST_DIR are left at src/server/index.ts's own defaults (8081, + // 0.0.0.0, ./dist relative to the Dockerfile's WORKDIR) — nothing here needs to differ + // from them, so only what actually must be supplied is set explicitly. + DATA_DIR: dataDir, + // generateJoinSecret.ts seeds this before main.ts ever runs, so it is never actually + // empty — the fallback only avoids threading `string | undefined` through `env`, which + // wants `Record`. + JOIN_SECRET: joinSecret ?? '', + }, + }, + // Health check, run on each polling interval. `checkPortListening` reports ready once the + // daemon binds `uiPort`; the 'ui' interface (interfaces.ts) exposes the same port. + ready: { + display: i18n('Multiplayer Server'), + fn: () => + sdk.healthCheck.checkPortListening(effects, uiPort, { + successMessage: i18n('The multiplayer server is ready'), + errorMessage: i18n('The multiplayer server is not ready'), + }), + }, + requires: [], + }) +}) diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts new file mode 100644 index 0000000..1507df4 --- /dev/null +++ b/startos/manifest/i18n.ts @@ -0,0 +1,41 @@ +export const short = { + en_US: 'Railroad operations board game — solitaire or multiplayer', + es_ES: 'Juego de mesa de operaciones ferroviarias: solitario o multijugador', + de_DE: 'Eisenbahn-Betriebs-Brettspiel — Solitär oder Mehrspieler', + pl_PL: 'Gra planszowa o zarządzaniu koleją — pasjans lub tryb wieloosobowy', + fr_FR: 'Jeu de plateau de gestion ferroviaire — solo ou multijoueur', +} + +export const long = { + en_US: + 'Station Master is a railroad operations game: switch cars, load and unload freight, and ' + + 'dispatch trains through a shared Division. This package runs the authoritative multiplayer ' + + 'server — 2 to 4 players connect from their browsers and play a competitive or cooperative ' + + 'game together. Solitaire needs no server at all; it runs entirely in the browser from the ' + + 'static client this server also hosts.', + es_ES: + 'Station Master es un juego de operaciones ferroviarias: maniobra vagones, carga y descarga ' + + 'mercancías, y despacha trenes por una División compartida. Este paquete ejecuta el servidor ' + + 'multijugador autoritativo — de 2 a 4 jugadores se conectan desde sus navegadores y juegan una ' + + 'partida competitiva o cooperativa. El modo solitario no necesita servidor: se ejecuta ' + + 'enteramente en el navegador desde el mismo cliente estático que este servidor aloja.', + de_DE: + 'Station Master ist ein Eisenbahn-Betriebsspiel: Waggons rangieren, Fracht laden und löschen ' + + 'und Züge durch eine gemeinsame Division disponieren. Dieses Paket betreibt den ' + + 'autoritativen Mehrspieler-Server — 2 bis 4 Spieler verbinden sich über den Browser und ' + + 'spielen gemeinsam kompetitiv oder kooperativ. Solitär benötigt keinen Server; es läuft ' + + 'vollständig im Browser über denselben statischen Client, den dieser Server ebenfalls hostet.', + pl_PL: + 'Station Master to gra o zarządzaniu koleją: manewruj wagonami, załaduj i rozładuj towar oraz ' + + 'dysponuj pociągami we wspólnej Dywizji. Ten pakiet uruchamia autorytatywny serwer trybu ' + + 'wieloosobowego — od 2 do 4 graczy łączy się z przeglądarek i rozgrywa partię rywalizacyjną ' + + 'lub kooperacyjną. Pasjans nie wymaga serwera — działa w całości w przeglądarce, z tego ' + + 'samego statycznego klienta, który hostuje ten serwer.', + fr_FR: + "Station Master est un jeu de gestion ferroviaire : manœuvrez des wagons, chargez et " + + "déchargez du fret, et dispatchez des trains à travers une Division partagée. Ce paquet " + + "exécute le serveur multijoueur faisant autorité — 2 à 4 joueurs se connectent depuis leur " + + "navigateur pour une partie compétitive ou coopérative. Le mode solitaire ne nécessite aucun " + + "serveur : il tourne entièrement dans le navigateur, à partir du même client statique que ce " + + "serveur héberge.", +} diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts new file mode 100644 index 0000000..ce4d3e5 --- /dev/null +++ b/startos/manifest/index.ts @@ -0,0 +1,29 @@ +import { setupManifest } from '@start9labs/start-sdk' +import { long, short } from './i18n' + +export const manifest = setupManifest({ + id: 'station-master', + title: 'Station Master', + // Not open source — Jesse's own project, packaged for his own StartOS box. + license: 'UNLICENSED', + packageRepo: 'https://draco.local:53871/Jesse.Markowitz/station-master-startos', + upstreamRepo: 'https://draco.local:53871/Jesse.Markowitz/station-master', + // No separate marketing site — the repo IS where there is more to learn. + marketingUrl: 'https://draco.local:53871/Jesse.Markowitz/station-master', + donationUrl: null, + description: { short, long }, + // Everything the server persists — game.json/index.json per game, turn timings, + // and this package's own store.json (the join secret) — lives on one volume. + volumes: ['data'], + images: { + // Built from source, not a published image: `main` here is the arbitrary + // image id, matched in main.ts's SubContainer.of({ imageId: 'main' }). + // The Dockerfile COPYs the pinned `station-master` git submodule — see + // UPDATING.md for how that pin is bumped. + main: { + source: { dockerBuild: { workdir: '.', dockerfile: './Dockerfile' } }, + arch: ['x86_64', 'aarch64'], + }, + }, + dependencies: {}, +}) diff --git a/startos/sdk.ts b/startos/sdk.ts new file mode 100644 index 0000000..04ae4b1 --- /dev/null +++ b/startos/sdk.ts @@ -0,0 +1,9 @@ +import { StartSdk } from '@start9labs/start-sdk' +import { manifest } from './manifest' + +/** + * Plumbing. DO NOT EDIT. + * + * The exported "sdk" const is used throughout this package codebase. + */ +export const sdk = StartSdk.of().withManifest(manifest).build(true) diff --git a/startos/utils.ts b/startos/utils.ts new file mode 100644 index 0000000..e4d2dd4 --- /dev/null +++ b/startos/utils.ts @@ -0,0 +1,8 @@ +// Station Master's server default (src/server/index.ts's `PORT` fallback) — kept identical here +// so the env var below is documentation, not a real override. +export const uiPort = 8081 + +// Where the volume is mounted inside the subcontainer, and so also `DATA_DIR`'s value. The +// server's own per-game persistence (`games//`, `index.json`) and this package's +// store.json (the join secret) share this one directory — see README's Volume and Data Layout. +export const dataDir = '/data' diff --git a/startos/versions/current.ts b/startos/versions/current.ts new file mode 100644 index 0000000..c1f06f8 --- /dev/null +++ b/startos/versions/current.ts @@ -0,0 +1,16 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const current = VersionInfo.of({ + version: '1.0.0:0', + releaseNotes: { + en_US: 'Initial StartOS package, bundling Station Master v0.5.1.', + es_ES: 'Paquete inicial para StartOS, con Station Master v0.5.1.', + de_DE: 'Erstes StartOS-Paket, mit Station Master v0.5.1.', + pl_PL: 'Pierwszy pakiet dla StartOS, zawiera Station Master v0.5.1.', + fr_FR: 'Premier paquet StartOS, avec Station Master v0.5.1.', + }, + migrations: { + up: async ({ effects }) => {}, + down: IMPOSSIBLE, + }, +}) diff --git a/startos/versions/index.ts b/startos/versions/index.ts new file mode 100644 index 0000000..e596b0c --- /dev/null +++ b/startos/versions/index.ts @@ -0,0 +1,7 @@ +import { VersionGraph } from '@start9labs/start-sdk' +import { current } from './current' + +export const versionGraph = VersionGraph.of({ + current, + other: [], +}) diff --git a/station-master b/station-master new file mode 160000 index 0000000..e76bd77 --- /dev/null +++ b/station-master @@ -0,0 +1 @@ +Subproject commit e76bd77099cf8427eb21b257daa5ca0329139c5f diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f032dc1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@start9labs/start-sdk/tsconfig.base.json", + "include": ["startos/**/*.ts", "node_modules/**/startos"] +}