From c51740c5898608683b05a3d3050ebaa45fca31ef Mon Sep 17 00:00:00 2001 From: Alexander Taborda Acosta Date: Sat, 8 Aug 2026 11:08:00 -0500 Subject: [PATCH] first commit --- .ai-workspace/TODO.md | 162 ++ .ai-workspace/UPDATING.md | 15 + .dockerignore | 6 + .gitignore | 6 + LICENSE | 37 + Makefile | 2 + README.md | 52 + assets/README.md | 3 + icon.svg | 1 + instructions.md | 35 + package-lock.json | 1670 +++++++++++++++++++++ package.json | 27 + startos/actions/configure.ts | 75 + startos/actions/index.ts | 4 + startos/backups.ts | 23 + startos/dependencies.ts | 3 + startos/fileModels/store.json.ts | 19 + startos/i18n/dictionaries/default.ts | 29 + startos/i18n/dictionaries/translations.ts | 3 + startos/i18n/index.ts | 5 + startos/index.ts | 10 + startos/init/index.ts | 18 + startos/init/seedStore.ts | 16 + startos/interfaces.ts | 24 + startos/main.ts | 124 ++ startos/manifest/i18n.ts | 20 + startos/manifest/index.ts | 31 + startos/sdk.ts | 4 + startos/utils.ts | 13 + startos/versions/current.ts | 16 + startos/versions/index.ts | 4 + tsconfig.json | 4 + 32 files changed, 2461 insertions(+) create mode 100644 .ai-workspace/TODO.md create mode 100644 .ai-workspace/UPDATING.md create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 assets/README.md 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/configure.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/index.ts create mode 100644 startos/init/seedStore.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 100644 tsconfig.json diff --git a/.ai-workspace/TODO.md b/.ai-workspace/TODO.md new file mode 100644 index 0000000..46d1284 --- /dev/null +++ b/.ai-workspace/TODO.md @@ -0,0 +1,162 @@ +# RomM for StartOS 0.4 — Rewrite TODO + +## Decision and scope + +Rewrite the package as a native StartOS 0.4 SDK-v2 service. The existing package never produced a working installation, so do **not** build an upgrade path from its StartOS 0.3-era manifest, configuration, database, or backups. + +The target is a clean installation of RomM. Existing users, if any, must install it as a new service and import their ROM library through a documented manual process. + +Initial application target: **RomM 3.5.0**, packaged as revision **`3.5.0:0`**. This keeps the application version stable while the wrapper is replaced. Upgrade RomM separately after the StartOS package works end to end. + +## Keep, replace, and discard + +### Keep as product requirements + +- Package identity: `romm`. +- Service name, description, license attribution, upstream links, and useful user-facing prose after checking them for accuracy. +- A persistent RomM library, resources, assets, configuration, and Redis data. +- A browser UI exposed through one StartOS `ui` interface on RomM's internal port `8080`. +- RomM's own authentication and first-run setup flow. +- Useful provider credentials/settings only when they are supported by the selected RomM release. +- Multi-architecture support for `x86_64` and `aarch64`. + +### Replace completely + +- `manifest.yaml` with `startos/manifest.ts` and SDK-v2 modules. +- `scripts/embassy.ts` and its Deno/`embassyd` API with SDK-v2 actions, file models, interfaces, daemons, health checks, backups, and versions. +- The custom source-build `Dockerfile` with pinned upstream multi-architecture images and small package-owned Dockerfiles only where necessary. +- `docker_entrypoint.sh` with the upstream RomM image's `/init` process. +- SQLite assumptions with a persistent MariaDB daemon. +- Legacy `tor-config`/`lan-config` declarations with a StartOS 0.4 interface. Gateway availability is controlled by StartOS. +- The empty `icon.png` with a valid icon no larger than 40 KiB. +- The legacy Makefile with the SDK-v2 `s9pk.mk` build. + +### Do not carry forward + +- Legacy migration entries or compatibility code for `3.5.0.0`. +- The old `.env` parser or shell-based `export $(...)` loading. +- SQLite or `DATABASE_URL` configuration. +- RAHasher/RetroAchievements code that is not part of the selected RomM release. +- Unsupported ScreenScraper, scheduler, watcher, logging, Redis, or auth environment variables. +- Claims that StartOS automatically authenticates the interface or provisions Tor access. +- Old backups; StartOS 0.3 and 0.4 backups are not compatible. + +## Phase 1 — Create the native package skeleton + +- [x] Preserve the legacy files temporarily for reference; do not use them in the new build. +- [x] Scaffold the package with the installed `start-cli s9pk init-package` workflow rather than copying another package wholesale. +- [x] Use `/Users/alextab93/code/gitea-runner-startos/` only as an SDK-v2 structural reference. +- [x] Add `package.json` and lockfile with `@start9labs/start-sdk` pinned to the workspace-supported 2.x version (currently `2.0.9`) and `@vercel/ncc`. +- [x] Replace the Makefile with `ARCHES := x86 arm` and include `node_modules/@start9labs/start-sdk/s9pk.mk`. +- [x] Create the standard current scaffold structure: + - [x] `startos/index.ts` + - [x] `startos/manifest/index.ts` + - [x] `startos/main.ts` + - [x] `startos/init/index.ts` + - [x] `startos/interfaces.ts` + - [x] `startos/actions/index.ts` + - [x] `startos/backups.ts` + - [x] `startos/fileModels/store.json.ts` + - [x] `startos/versions/index.ts` + - [x] `startos/versions/current.ts` + - [x] `startos/i18n/index.ts` + - [x] `assets/` +- [x] Export the six required ABI functions from `startos/index.ts`: `createBackup`, `main`, `init`, `uninit`, `manifest`, and `actions`. +- [x] Set the package version to `3.5.0:0`; there is no predecessor node because this is a clean rewrite. + +## Phase 2 — Define the runtime topology + +- [x] Pin `rommapp/romm:3.5.0` by immutable digest after verifying both `amd64` and `arm64` manifests. +- [x] Use the upstream image entrypoint and `/init` command as PID 1; enable init semantics if required by the SDK. +- [x] Do not rebuild RomM from source in this package. +- [x] Add a pinned, multi-architecture MariaDB image as a second daemon. +- [x] Create persistent storage with clear ownership: + - [x] RomM data mounted where the official image expects `/romm/library`, `/romm/resources`, `/romm/assets`, and `/romm/config`. + - [x] Embedded Redis data mounted at `/redis-data`. + - [x] MariaDB data mounted at `/var/lib/mysql`. +- [x] Prefer separate StartOS volume IDs such as `main` and `database`; there is no legacy data-layout constraint. +- [x] Generate MariaDB root password, application password, and `ROMM_AUTH_SECRET_KEY` once during initialization. +- [x] Store secrets with typed SDK file models/store helpers; never regenerate them on a settings change. +- [x] Pass only variables supported by RomM 3.5.0, including the MariaDB host, database, user, and password. +- [x] Leave `REDIS_HOST` unset if the official RomM image is expected to manage its embedded Redis; do not pass an empty `REDIS_PORT`. +- [x] Make the RomM daemon depend on MariaDB readiness. +- [x] Run both containers with the least privilege compatible with their official initialization processes. + +## Phase 3 — Interface, health, and configuration + +- [x] Define one `MultiHost`/`createInterface` UI interface bound to internal port `8080`. +- [x] Label it clearly as the RomM web interface and rely on RomM's native authentication. +- [x] Do not hard-code Tor or LAN URLs; document that StartOS gateway choices control availability. +- [x] Add MariaDB readiness using a database-native check or listening-port check. +- [x] Add RomM readiness using `checkPortListening(8080)`; `/api/heartbeat` was also confirmed during the local runtime smoke test. +- [x] Expose a small configuration action only for settings verified against the exact RomM 3.5.0 environment-variable documentation/source. +- [x] Treat credentials as secrets in action inputs and stored files. +- [x] Ensure values containing spaces, quotes, dollar signs, or newlines are passed through structured environment handling, never shell interpolation. +- [x] Evaluate safe setup guidance: keep it in `instructions.md`; the action never prefills or reveals stored secrets. + +## Phase 4 — Backups and restore + +- [x] Back up the MariaDB database with the SDK MySQL/MariaDB dump helper. +- [x] Back up RomM configuration and application-owned metadata. +- [x] Include the ROM library in routine StartOS backups for complete restores; document that backups may be very large. +- [x] Document an independent backup strategy for irreplaceable ROM files even though the library is included. +- [x] Include Redis data as part of the complete `main` volume and document that choice. +- [ ] Test restore into an empty service and verify database records, configuration, login, and library visibility. + +## Phase 5 — Documentation and assets + +- [x] Replace the zero-byte icon with an upstream-authorized PNG or SVG that meets StartOS size/format requirements. +- [x] Repair or rewrite `README.md`; remove embedded NUL bytes and corrupted tree glyphs. +- [x] Rewrite `instructions.md` for a clean StartOS 0.4 install. +- [x] Document the RomM first-run administrator setup. +- [x] Document required storage, supported gateways, native RomM authentication, provider credentials, and backup limitations accurately. +- [x] Add a clear statement that the abandoned StartOS 0.3 package is not upgrade-compatible and its backups cannot be restored into this package. +- [x] Include a manual ROM import guide for users who experimented with the old wrapper. +- [x] Record all image tags and immutable digests used by the package. + +## Phase 6 — Remove the abandoned wrapper + +Removed after native build/pack and isolated runtime validation. Full StartOS installation testing remains explicitly unchecked below. + +- [x] Remove `manifest.yaml`. +- [x] Remove `scripts/embassy.ts` and the generated legacy JavaScript, if present. +- [x] Remove `docker_entrypoint.sh`. +- [x] Remove the old custom `Dockerfile`, unless a new minimal package Dockerfile intentionally replaces it. +- [x] Remove `icon_note.txt` after installing the real icon. +- [x] Remove obsolete documentation and unsupported configuration references. +- [x] Confirm source and user documentation contain no references to `embassyd`, `DATABASE_URL`, SQLite, RAHasher, `REDIS_PORT=`, legacy `tor-config`, or legacy `lan-config` (the historical checklist wording is excluded from this search). + +## Validation gates + +Do not mark the rewrite complete until every applicable gate passes. + +- [x] Install dependencies reproducibly from the committed lockfile (`npm ci`). +- [x] Type-check and bundle `startos/index.ts` to `javascript/index.js`. +- [x] Run `start-cli s9pk list-ingredients .` successfully. +- [x] Build the package for both `x86_64` and `aarch64`. +- [x] Pack and verify the `.s9pk` with the current StartOS tooling. +- [x] Inspect both packed artifacts: SDK `2.0.9`, package `3.5.0:0`, target-specific packed images, `main`/`database` volumes, six ABI exports, one UI interface in bundled source, and no additional package permissions. +- [ ] Install on a clean StartOS 0.4 system. +- [x] Verify the pinned MariaDB runtime initializes once and survives service restart (isolated ARM64 runtime smoke test). +- [x] Verify the pinned RomM runtime reaches ready state, completes first-run admin setup, and serves the browser UI (isolated ARM64 runtime smoke test). +- [ ] Verify login, logout, session persistence, and secret persistence across configuration changes and restarts. +- [ ] Import a small representative ROM set and verify scan, metadata, artwork, launch/download paths, and background workers. +- [ ] Exercise stop/start, reboot, update-to-same-build, backup, restore, and uninstall/reinstall behavior. +- [ ] Verify both Tor and LAN access only through gateways enabled by the StartOS administrator. +- [ ] Repeat smoke tests on both supported CPU architectures. + +### Current external validation blocker + +- The configured StartOS development host is `https://dev-vm.local`, but it is currently unreachable from this workstation. The install and appliance-only checks above must stay unchecked until a StartOS 0.4 host is online and authenticated. +- Local ARM64 container smoke test completed: MariaDB initialized 11 RomM schema tables, `/api/heartbeat` reported RomM `3.5.0`, every declared persistent mount was writable, and database/library state survived restart. A real administrator was created through RomM's CSRF-protected first-run API, the UI root returned HTML, login and authenticated identity succeeded, the same session remained valid after restart with the fixed auth secret, logout invalidated the session, and the setup wizard switched off. +- The packaging repository URLs available from the abandoned manifest and local username do not currently resolve on GitHub. Confirm the final repository URL before publishing. + +## Definition of done + +- A clean StartOS 0.4 install starts RomM and MariaDB without manual shell intervention. +- The UI is reachable through the declared StartOS interface and protected by RomM authentication. +- Persistent data and generated secrets survive restarts and configuration changes. +- Backup and restore behavior is tested and documented, including any ROM-library exclusion. +- The package builds reproducibly for both architectures and passes StartOS pack/verify checks. +- Documentation describes only behavior confirmed on a real StartOS 0.4 installation. +- No runtime or packaging dependency remains on the abandoned StartOS 0.3 wrapper. diff --git a/.ai-workspace/UPDATING.md b/.ai-workspace/UPDATING.md new file mode 100644 index 0000000..d2565ef --- /dev/null +++ b/.ai-workspace/UPDATING.md @@ -0,0 +1,15 @@ +# Updating RomM + +This package intentionally begins with RomM `3.5.0` so the StartOS wrapper can be validated independently from an application upgrade. + +For a future update: + +1. Read the RomM release and database migration notes. +2. Verify the image tag and both `amd64` and `arm64` manifests. +3. Pin the new multi-architecture digest in `startos/manifest/index.ts`. +4. Reinspect the entrypoint, ports, volumes, users, and environment variables. +5. Update `startos/versions/current.ts`; add a historical node only for an explicit package migration. +6. Update README and instructions for user-visible behavior. +7. Test install, upgrade, login, scan, restart, backup, and restore on StartOS. + +Do not restore compatibility with the abandoned StartOS 0.3 wrapper. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ade084c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.ai-workspace +node_modules +javascript +docker-images +*.s9pk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6425515 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +javascript/ +docker-images/ +*.s9pk +*.s9pk.* +result diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..838f845 --- /dev/null +++ b/LICENSE @@ -0,0 +1,37 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +RomM (ROM Manager) - Self-hosted ROM management system +Copyright (C) 2024 RomM Authors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Additional permissions under GNU AGPL version 3 section 7: + +If you modify this Program, or any covered work, by linking or +combining it with other code, such other code is not for that reason +alone subject to any of the requirements of the GNU Affero General +Public License. + +--- + +This Start9 package wrapper is released under the same AGPL-3.0 license +as the original RomM project to maintain license compatibility. + +Start9 Wrapper Components: +- Package configuration and build scripts +- Docker container adaptations for Start9 +- Integration scripts and health checks + +For the complete license text, visit: https://www.gnu.org/licenses/agpl-3.0.html \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4167a7d --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +ARCHES := x86 arm +include node_modules/@start9labs/start-sdk/s9pk.mk diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d51d29 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# RomM for StartOS + +This repository packages [RomM](https://github.com/rommapp/romm) 3.5.0 as a native StartOS 0.4 SDK-v2 service. + +RomM scans, enriches, browses, and manages a personal game library from a web interface. This package uses the official RomM container and a private MariaDB sidecar. RomM's bundled Redis instance, web server, migrations, watcher, worker, and scheduler are started by the upstream `/init` process. + +## Architecture + +- `rommapp/romm:3.5.0` serves the UI on internal port `8080`. +- `mariadb:11.4.5` stores RomM metadata on internal port `3306`. +- The daemons share the service network namespace; MariaDB listens only inside the package. +- RomM starts only after the MariaDB health check succeeds. +- StartOS exposes a single `ui` interface. The administrator chooses which installed gateways may publish an address. +- Authentication is provided by RomM, not by the StartOS interface proxy. + +## Persistent data + +The `main` volume contains `library/`, `resources/`, `assets/`, `config/`, `redis-data/`, and the package's `store.json`. The `database` volume contains MariaDB at `/var/lib/mysql`. + +Internal MariaDB credentials and `ROMM_AUTH_SECRET_KEY` are generated once during a clean install. The Configure Metadata Providers action stores the exact optional variables supported by RomM 3.5.0: `IGDB_CLIENT_ID`, `IGDB_CLIENT_SECRET`, `MOBYGAMES_API_KEY`, and `STEAMGRIDDB_API_KEY`. + +## Backups + +Backups create a logical MariaDB dump and copy the `main` volume. The ROM library is included for correctness, so backups may be large. StartOS stops the service while creating or restoring a backup. + +## Legacy package policy + +This is a clean rewrite. The abandoned StartOS 0.3 wrapper never formed a supported release and has no upgrade, database, or backup compatibility with this package. Users must copy only their ROM files into a clean installation and let RomM scan them again. + +## Images + +Both images are pinned to immutable multi-architecture OCI indexes: + +- `rommapp/romm:3.5.0@sha256:9ff83725e98e5dfc0b871cb88ca378c539fad66b7afcbe6aad562d2b84d5b802` +- `mariadb:11.4.5@sha256:49117dcc565cf51aa57ac5fca59ab31213402ff0eae6ffc13c46a37b938f7e4b` + +The verified Linux platforms are `amd64` and `arm64`. + +## Build + +```sh +npm ci +npm run check +npm run build +make +``` + +The package uses `@start9labs/start-sdk` 2.0.9. A release requires installation, login, library import, restart, backup, and restore testing on real StartOS 0.4 hardware for both supported architectures. + +## License + +RomM and this package are distributed under AGPL-3.0. See [LICENSE](LICENSE). diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..70eb994 --- /dev/null +++ b/assets/README.md @@ -0,0 +1,3 @@ +# Marketplace assets + +RomM's upstream logo is used as `icon.svg`. Add screenshots only after capturing the native package running on StartOS; do not use mockups as release evidence. diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..11649a7 --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ ++ diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..88bbb97 --- /dev/null +++ b/instructions.md @@ -0,0 +1,35 @@ +# RomM + +RomM provides a browser interface for organizing, enriching, and browsing your game library. + +## First start + +1. Start RomM and wait for both Database and Web Interface health checks to become ready. Initial database setup can take several minutes. +2. Open **Interfaces → RomM Web Interface**. +3. Complete RomM's first-run administrator registration in the web interface. +4. Keep RomM authentication enabled. StartOS does not add authentication to this interface. + +The StartOS administrator controls where the interface is reachable. LAN addresses are normally available first. Tor requires the separate Tor service and must be enabled for this interface by the administrator. + +## Metadata providers + +RomM works without external metadata credentials, but enrichment is limited. Run **Actions → Configure Metadata Providers** to save optional credentials for IGDB, MobyGames, and SteamGridDB. The action never displays saved secret values. Restart RomM after saving or clearing credentials. + +## Importing ROMs + +Place files in the library using the folder layout documented for RomM 3.5.0, then start a scan from the RomM web interface. Test with a small library before copying a large collection. + +If you experimented with the abandoned StartOS 0.3 wrapper, do not copy its database, `.env`, Redis data, or backup. Copy only ROM files into a clean installation and let RomM rebuild its metadata. + +## Storage and backups + +The StartOS backup contains the MariaDB database, RomM configuration, generated secrets, artwork/resources, Redis data, and the ROM library. Because ROM libraries can be very large, confirm that the backup destination has enough free space and maintain an independent copy of irreplaceable ROM files. + +Backups from the old StartOS 0.3 wrapper cannot be restored into this native StartOS 0.4 package. After restoring, verify sign-in, library visibility, and a representative game before treating the restore as successful. + +## Security + +- Use a strong RomM administrator password. +- Enable only the StartOS gateway addresses you need. +- Treat metadata-provider credentials as secrets. +- Do not assume a Tor or public address exists unless it is shown and enabled in the Interfaces tab. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..31be85e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1670 @@ +{ + "name": "romm-startos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "romm-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.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "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.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "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..06d6003 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "romm-startos", + "private": true, + "scripts": { + "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", + "check": "tsc --noEmit", + "format": "prettier --write startos" + }, + "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/configure.ts b/startos/actions/configure.ts new file mode 100644 index 0000000..e038668 --- /dev/null +++ b/startos/actions/configure.ts @@ -0,0 +1,75 @@ +import { sdk } from '../sdk' +import { storeJson } from '../fileModels/store.json' +import { i18n } from '../i18n' + +const { InputSpec, Value } = sdk + +const inputSpec = InputSpec.of({ + igdbClientId: Value.text({ + name: i18n('IGDB Client ID'), + description: i18n('Optional Twitch application client ID used by IGDB.'), + required: false, + default: null, + }), + igdbClientSecret: Value.text({ + name: i18n('IGDB Client Secret'), + description: i18n('Optional Twitch application secret used by IGDB.'), + required: false, + masked: true, + default: null, + }), + mobygamesApiKey: Value.text({ + name: i18n('MobyGames API Key'), + description: i18n('Optional API key used to retrieve MobyGames metadata.'), + required: false, + masked: true, + default: null, + }), + steamGridDbApiKey: Value.text({ + name: i18n('SteamGridDB API Key'), + description: i18n('Optional API key used to retrieve SteamGridDB artwork.'), + required: false, + masked: true, + default: null, + }), +}) + +export const configure = sdk.Action.withInput( + 'configure', + async () => ({ + name: i18n('Configure Metadata Providers'), + description: i18n( + 'Save optional API credentials supported by RomM 3.5.0.', + ), + warning: i18n( + 'Saved values are passed to RomM after the service is restarted. Leave a field blank to remove it.', + ), + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + inputSpec, + async () => ({ + igdbClientId: null, + igdbClientSecret: null, + mobygamesApiKey: null, + steamGridDbApiKey: null, + }), + async ({ effects, input }) => { + await storeJson.merge(effects, { + igdbClientId: input.igdbClientId ?? '', + igdbClientSecret: input.igdbClientSecret ?? '', + mobygamesApiKey: input.mobygamesApiKey ?? '', + steamGridDbApiKey: input.steamGridDbApiKey ?? '', + }) + + return { + version: '1', + title: i18n('Configuration Saved'), + message: i18n( + 'Restart RomM to apply the metadata provider configuration.', + ), + result: null, + } + }, +) diff --git a/startos/actions/index.ts b/startos/actions/index.ts new file mode 100644 index 0000000..99e118d --- /dev/null +++ b/startos/actions/index.ts @@ -0,0 +1,4 @@ +import { sdk } from '../sdk' +import { configure } from './configure' + +export const actions = sdk.Actions.of().addAction(configure) diff --git a/startos/backups.ts b/startos/backups.ts new file mode 100644 index 0000000..f1febae --- /dev/null +++ b/startos/backups.ts @@ -0,0 +1,23 @@ +import { sdk } from './sdk' +import { storeJson } from './fileModels/store.json' +import { databaseName, databaseUser } from './utils' + +export const { createBackup, restoreInit } = sdk.setupBackups( + async ({ effects }) => + sdk.Backups.withMysqlDump({ + imageId: 'mariadb', + dbVolume: 'database', + datadir: '/var/lib/mysql', + database: databaseName, + user: databaseUser, + password: async () => { + const store = await storeJson.read().once() + if (!store?.databasePassword) { + throw new Error('RomM database password is missing') + } + return store.databasePassword + }, + engine: 'mariadb', + readyTimeout: 120000, + }).addVolume('main'), +) diff --git a/startos/dependencies.ts b/startos/dependencies.ts new file mode 100644 index 0000000..f77d698 --- /dev/null +++ b/startos/dependencies.ts @@ -0,0 +1,3 @@ +import { sdk } from './sdk' + +export const setDependencies = sdk.setupDependencies(async () => ({})) diff --git a/startos/fileModels/store.json.ts b/startos/fileModels/store.json.ts new file mode 100644 index 0000000..5624893 --- /dev/null +++ b/startos/fileModels/store.json.ts @@ -0,0 +1,19 @@ +import { FileHelper, z } from '@start9labs/start-sdk' +import { sdk } from '../sdk' + +const shape = z + .object({ + databaseRootPassword: z.string().catch(''), + databasePassword: z.string().catch(''), + authSecret: z.string().catch(''), + igdbClientId: z.string().catch(''), + igdbClientSecret: z.string().catch(''), + mobygamesApiKey: z.string().catch(''), + steamGridDbApiKey: z.string().catch(''), + }) + .strip() + +export const storeJson = FileHelper.json( + { base: sdk.volumes.main, subpath: './store.json' }, + shape, +) diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts new file mode 100644 index 0000000..c8479d4 --- /dev/null +++ b/startos/i18n/dictionaries/default.ts @@ -0,0 +1,29 @@ +export const DEFAULT_LANG = 'en_US' + +const dict = { + Database: 0, + 'MariaDB is ready': 1, + 'MariaDB is not ready': 2, + 'Web Interface': 3, + 'RomM is ready': 4, + 'RomM is not ready': 5, + 'RomM Web Interface': 6, + 'Browse, scan, and manage your game library': 7, + 'IGDB Client ID': 8, + 'Optional Twitch application client ID used by IGDB.': 9, + 'IGDB Client Secret': 10, + 'Optional Twitch application secret used by IGDB.': 11, + 'MobyGames API Key': 12, + 'Optional API key used to retrieve MobyGames metadata.': 13, + 'SteamGridDB API Key': 14, + 'Optional API key used to retrieve SteamGridDB artwork.': 15, + 'Configure Metadata Providers': 16, + 'Save optional API credentials supported by RomM 3.5.0.': 17, + 'Saved values are passed to RomM after the service is restarted. Leave a field blank to remove it.': 18, + 'Configuration Saved': 19, + 'Restart RomM to apply the metadata provider configuration.': 20, +} as const + +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..98f9348 --- /dev/null +++ b/startos/i18n/dictionaries/translations.ts @@ -0,0 +1,3 @@ +import { LangDict } from './default' + +export default {} satisfies Record diff --git a/startos/i18n/index.ts b/startos/i18n/index.ts new file mode 100644 index 0000000..1849d6d --- /dev/null +++ b/startos/i18n/index.ts @@ -0,0 +1,5 @@ +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..1c08f96 --- /dev/null +++ b/startos/index.ts @@ -0,0 +1,10 @@ +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/index.ts b/startos/init/index.ts new file mode 100644 index 0000000..e4354d1 --- /dev/null +++ b/startos/init/index.ts @@ -0,0 +1,18 @@ +import { actions } from '../actions' +import { restoreInit } from '../backups' +import { setDependencies } from '../dependencies' +import { setInterfaces } from '../interfaces' +import { sdk } from '../sdk' +import { versionGraph } from '../versions' +import { seedStore } from './seedStore' + +export const init = sdk.setupInit( + restoreInit, + versionGraph, + seedStore, + setInterfaces, + setDependencies, + actions, +) + +export const uninit = sdk.setupUninit(versionGraph) diff --git a/startos/init/seedStore.ts b/startos/init/seedStore.ts new file mode 100644 index 0000000..4822249 --- /dev/null +++ b/startos/init/seedStore.ts @@ -0,0 +1,16 @@ +import { utils } from '@start9labs/start-sdk' +import { storeJson } from '../fileModels/store.json' +import { sdk } from '../sdk' + +const secret = () => + utils.getDefaultString({ charset: 'a-z,A-Z,0-9', len: 64 }) + +export const seedStore = sdk.setupOnInit(async (effects, kind) => { + if (kind !== 'install') return + + await storeJson.merge(effects, { + databaseRootPassword: secret(), + databasePassword: secret(), + authSecret: secret(), + }) +}) diff --git a/startos/interfaces.ts b/startos/interfaces.ts new file mode 100644 index 0000000..33419bd --- /dev/null +++ b/startos/interfaces.ts @@ -0,0 +1,24 @@ +import { sdk } from './sdk' +import { i18n } from './i18n' +import { uiHostId, uiPort } from './utils' + +export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { + const host = sdk.MultiHost.of(effects, uiHostId) + const origin = await host.bindPort(uiPort, { + protocol: 'http', + preferredExternalPort: 80, + }) + const ui = sdk.createInterface(effects, { + name: i18n('RomM Web Interface'), + id: 'ui', + description: i18n('Browse, scan, and manage your game library'), + type: 'ui', + masked: false, + schemeOverride: null, + username: null, + path: '', + query: {}, + }) + + return [await origin.export([ui])] +}) diff --git a/startos/main.ts b/startos/main.ts new file mode 100644 index 0000000..b312aad --- /dev/null +++ b/startos/main.ts @@ -0,0 +1,124 @@ +import { sdk } from './sdk' +import { i18n } from './i18n' +import { storeJson } from './fileModels/store.json' +import { + databaseName, + databasePort, + databaseUser, + mainMounts, + uiPort, +} from './utils' + +export const main = sdk.setupMain(async ({ effects }) => { + const store = await storeJson.read().const(effects) + if ( + !store?.databaseRootPassword || + !store.databasePassword || + !store.authSecret + ) { + throw new Error('RomM internal secrets have not been initialized') + } + + const mariadb = sdk.SubContainer.of( + effects, + { imageId: 'mariadb' }, + sdk.Mounts.of().mountVolume({ + volumeId: 'database', + subpath: null, + mountpoint: '/var/lib/mysql', + readonly: false, + }), + 'romm-mariadb-sub', + ) + + const romm = sdk.SubContainer.of( + effects, + { imageId: 'romm' }, + sdk.Mounts.of() + .mountVolume({ + volumeId: 'main', + subpath: 'library', + mountpoint: mainMounts.library, + readonly: false, + }) + .mountVolume({ + volumeId: 'main', + subpath: 'resources', + mountpoint: mainMounts.resources, + readonly: false, + }) + .mountVolume({ + volumeId: 'main', + subpath: 'assets', + mountpoint: mainMounts.assets, + readonly: false, + }) + .mountVolume({ + volumeId: 'main', + subpath: 'config', + mountpoint: mainMounts.config, + readonly: false, + }) + .mountVolume({ + volumeId: 'main', + subpath: 'redis-data', + mountpoint: mainMounts.redis, + readonly: false, + }), + 'romm-app-sub', + ) + + return sdk.Daemons.of(effects) + .addDaemon('mariadb', { + subcontainer: mariadb, + exec: { + command: sdk.useEntrypoint(), + runAsInit: true, + env: { + MARIADB_ROOT_PASSWORD: store.databaseRootPassword, + MARIADB_DATABASE: databaseName, + MARIADB_USER: databaseUser, + MARIADB_PASSWORD: store.databasePassword, + }, + }, + ready: { + display: i18n('Database'), + gracePeriod: 120000, + fn: () => + sdk.healthCheck.checkPortListening(effects, databasePort, { + successMessage: i18n('MariaDB is ready'), + errorMessage: i18n('MariaDB is not ready'), + }), + }, + requires: [], + }) + .addDaemon('romm', { + subcontainer: romm, + exec: { + command: sdk.useEntrypoint(), + runAsInit: true, + env: { + DB_HOST: '127.0.0.1', + DB_PORT: String(databasePort), + DB_NAME: databaseName, + DB_USER: databaseUser, + DB_PASSWD: store.databasePassword, + ROMM_AUTH_SECRET_KEY: store.authSecret, + IGDB_CLIENT_ID: store.igdbClientId, + IGDB_CLIENT_SECRET: store.igdbClientSecret, + MOBYGAMES_API_KEY: store.mobygamesApiKey, + STEAMGRIDDB_API_KEY: store.steamGridDbApiKey, + }, + }, + ready: { + display: i18n('Web Interface'), + gracePeriod: 180000, + fn: () => + sdk.healthCheck.checkPortListening(effects, uiPort, { + successMessage: i18n('RomM is ready'), + errorMessage: i18n('RomM is not ready'), + }), + }, + requires: ['mariadb'], + }) +}) diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts new file mode 100644 index 0000000..14763bd --- /dev/null +++ b/startos/manifest/i18n.ts @@ -0,0 +1,20 @@ +export const short = { + en_US: 'A self-hosted ROM manager for browsing and organizing game libraries', + es_ES: 'Un gestor de ROM autoalojado para explorar y organizar bibliotecas de juegos', + de_DE: 'Ein selbst gehosteter ROM-Manager zum Durchsuchen und Organisieren von Spielesammlungen', + pl_PL: 'Samodzielnie hostowany menedżer ROM-ów do przeglądania i organizowania bibliotek gier', + fr_FR: 'Un gestionnaire de ROM auto-hébergé pour parcourir et organiser les ludothèques', +} + +export const long = { + en_US: + 'RomM scans, enriches, browses, and manages a personal game library through a responsive web interface. This package runs RomM with a private MariaDB database and persistent library storage.', + es_ES: + 'RomM analiza, enriquece, explora y administra una biblioteca personal de juegos mediante una interfaz web adaptable. Este paquete ejecuta RomM con una base de datos MariaDB privada y almacenamiento persistente.', + de_DE: + 'RomM scannt, ergänzt, durchsucht und verwaltet eine persönliche Spielesammlung über eine responsive Weboberfläche. Dieses Paket betreibt RomM mit einer privaten MariaDB-Datenbank und persistentem Speicher.', + pl_PL: + 'RomM skanuje, wzbogaca, przegląda i zarządza osobistą biblioteką gier przez responsywny interfejs WWW. Ten pakiet uruchamia RomM z prywatną bazą MariaDB i trwałym magazynem.', + fr_FR: + 'RomM analyse, enrichit, parcourt et gère une ludothèque personnelle dans une interface web adaptative. Ce paquet exécute RomM avec une base MariaDB privée et un stockage persistant.', +} diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts new file mode 100644 index 0000000..b14bff4 --- /dev/null +++ b/startos/manifest/index.ts @@ -0,0 +1,31 @@ +import { setupManifest } from '@start9labs/start-sdk' +import { long, short } from './i18n' + +export const manifest = setupManifest({ + id: 'romm', + title: 'RomM', + license: 'AGPL-3.0', + packageRepo: 'https://github.com/alex/romm-startos', + upstreamRepo: 'https://github.com/rommapp/romm', + marketingUrl: 'https://romm.app/', + donationUrl: null, + description: { short, long }, + volumes: ['main', 'database'], + images: { + romm: { + source: { + dockerTag: + 'rommapp/romm:3.5.0@sha256:9ff83725e98e5dfc0b871cb88ca378c539fad66b7afcbe6aad562d2b84d5b802', + }, + arch: ['x86_64', 'aarch64'], + }, + mariadb: { + source: { + dockerTag: + 'mariadb:11.4.5@sha256:49117dcc565cf51aa57ac5fca59ab31213402ff0eae6ffc13c46a37b938f7e4b', + }, + arch: ['x86_64', 'aarch64'], + }, + }, + dependencies: {}, +}) diff --git a/startos/sdk.ts b/startos/sdk.ts new file mode 100644 index 0000000..4bf7c2c --- /dev/null +++ b/startos/sdk.ts @@ -0,0 +1,4 @@ +import { StartSdk } from '@start9labs/start-sdk' +import { manifest } from './manifest' + +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..bd5049d --- /dev/null +++ b/startos/utils.ts @@ -0,0 +1,13 @@ +export const uiPort = 8080 +export const databasePort = 3306 +export const uiHostId = 'ui' +export const databaseName = 'romm' +export const databaseUser = 'romm' + +export const mainMounts = { + library: '/romm/library', + resources: '/romm/resources', + assets: '/romm/assets', + config: '/romm/config', + redis: '/redis-data', +} as const diff --git a/startos/versions/current.ts b/startos/versions/current.ts new file mode 100644 index 0000000..6c98323 --- /dev/null +++ b/startos/versions/current.ts @@ -0,0 +1,16 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const current = VersionInfo.of({ + version: '3.5.0:0', + releaseNotes: { + en_US: 'Initial native StartOS 0.4 package for RomM 3.5.0.', + es_ES: 'Paquete nativo inicial de StartOS 0.4 para RomM 3.5.0.', + de_DE: 'Erstes natives StartOS-0.4-Paket für RomM 3.5.0.', + pl_PL: 'Pierwszy natywny pakiet StartOS 0.4 dla RomM 3.5.0.', + fr_FR: 'Premier paquet StartOS 0.4 natif pour RomM 3.5.0.', + }, + migrations: { + up: async () => {}, + down: IMPOSSIBLE, + }, +}) diff --git a/startos/versions/index.ts b/startos/versions/index.ts new file mode 100644 index 0000000..a4a9841 --- /dev/null +++ b/startos/versions/index.ts @@ -0,0 +1,4 @@ +import { VersionGraph } from '@start9labs/start-sdk' +import { current } from './current' + +export const versionGraph = VersionGraph.of({ current, other: [] }) 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"] +}