From 6f0378acbefec62e913868058898b9106b776718 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 12 Feb 2026 21:11:08 -0700 Subject: [PATCH 01/13] update to 040 --- .github/workflows/buildService.yml | 39 +- .github/workflows/releaseService.yml | 71 +- .gitignore | 7 +- CONTRIBUTING.md | 15 + Dockerfile | 54 +- Makefile | 63 +- README.md | 142 - assets/README.md | 1 + docker_entrypoint.sh | 24 - instructions.md | 19 - manifest.yaml | 94 - package-lock.json | 254 ++ package.json | 23 + s9pk.mk | 127 + scripts/deps.ts | 1 - scripts/embassy.js | 4197 --------------------- scripts/embassy.ts | 2 - scripts/procedures/healthChecks.ts | 5 - scripts/procedures/migrations.ts | 4 - startos/actions/index.ts | 3 + startos/backups.ts | 5 + startos/dependencies.ts | 5 + startos/i18n/dictionaries/default.ts | 19 + startos/i18n/dictionaries/translations.ts | 32 + startos/i18n/index.ts | 8 + startos/index.ts | 11 + startos/init/index.ts | 16 + startos/install/versionGraph.ts | 8 + startos/install/versions/index.ts | 2 + startos/install/versions/v1.0.9.1.b0.ts | 16 + startos/interfaces.ts | 25 + startos/main.ts | 32 + startos/manifest/i18n.ts | 24 + startos/manifest/index.ts | 23 + startos/sdk.ts | 9 + startos/utils.ts | 1 + tsconfig.json | 11 + 37 files changed, 705 insertions(+), 4687 deletions(-) create mode 100644 CONTRIBUTING.md delete mode 100644 README.md create mode 100644 assets/README.md delete mode 100755 docker_entrypoint.sh delete mode 100644 instructions.md delete mode 100644 manifest.yaml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 s9pk.mk delete mode 100644 scripts/deps.ts delete mode 100644 scripts/embassy.js delete mode 100644 scripts/embassy.ts delete mode 100644 scripts/procedures/healthChecks.ts delete mode 100644 scripts/procedures/migrations.ts create mode 100644 startos/actions/index.ts create mode 100644 startos/backups.ts create mode 100644 startos/dependencies.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/install/versionGraph.ts create mode 100644 startos/install/versions/index.ts create mode 100644 startos/install/versions/v1.0.9.1.b0.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 tsconfig.json diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml index c2ae643..feb2ae9 100644 --- a/.github/workflows/buildService.yml +++ b/.github/workflows/buildService.yml @@ -4,33 +4,20 @@ on: workflow_dispatch: pull_request: paths-ignore: ['*.md'] - branches: ['main', 'master'] + branches: ['main', 'master', 'update/040'] push: paths-ignore: ['*.md'] - branches: ['main', 'master'] + branches: ['main', 'master', 'update/040'] -jobs: - BuildPackage: - runs-on: ubuntu-latest - steps: - - name: Prepare StartOS SDK - uses: Start9Labs/sdk@v1 - - - name: Checkout services repository - uses: actions/checkout@v4 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true - - name: Build the service package - id: build - run: | - git submodule update --init --recursive - start-sdk init - make - PACKAGE_ID=$(yq -oy ".id" manifest.*) - echo "package_id=$PACKAGE_ID" >> $GITHUB_ENV - shell: bash - -# - name: Upload .s9pk -# uses: actions/upload-artifact@v4 -# with: -# name: ${{ env.package_id }}.s9pk -# path: ./${{ env.package_id }}.s9pk +jobs: + build: + if: github.event.pull_request.draft == false + uses: start9labs/shared-workflows/.github/workflows/buildService.yml@master + # with: + # FREE_DISK_SPACE: true + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml index f1eebb4..17a0b91 100644 --- a/.github/workflows/releaseService.yml +++ b/.github/workflows/releaseService.yml @@ -6,66 +6,15 @@ on: - 'v*.*' jobs: - ReleasePackage: - runs-on: ubuntu-latest + release: + uses: start9labs/shared-workflows/.github/workflows/releaseService.yml@master + with: + # FREE_DISK_SPACE: true + REGISTRY: ${{ vars.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 - steps: - - name: Prepare StartOS SDK - uses: Start9Labs/sdk@v1 - - - name: Checkout services repository - uses: actions/checkout@v4 - - - name: Build the service package - run: | - git submodule update --init --recursive - start-sdk init - make - - - name: Setting package ID and title from the manifest - id: package - run: | - echo "package_id=$(yq -oy ".id" manifest.*)" >> $GITHUB_ENV - echo "package_title=$(yq -oy ".title" manifest.*)" >> $GITHUB_ENV - shell: bash - - - name: Generate sha256 checksum - run: | - PACKAGE_ID=${{ env.package_id }} - sha256sum ${PACKAGE_ID}.s9pk > ${PACKAGE_ID}.s9pk.sha256 - shell: bash - - - name: Generate changelog - run: | - PACKAGE_ID=${{ env.package_id }} - echo "## What's Changed" > change-log.txt - yq -oy '.release-notes' manifest.* >> change-log.txt - echo "## SHA256 Hash" >> change-log.txt - echo '```' >> change-log.txt - sha256sum ${PACKAGE_ID}.s9pk >> change-log.txt - echo '```' >> change-log.txt - shell: bash - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - name: ${{ env.package_title }} ${{ github.ref_name }} - prerelease: true - body_path: change-log.txt - files: | - ./${{ env.package_id }}.s9pk - ./${{ env.package_id }}.s9pk.sha256 - - - name: Publish to Registry - env: - S9USER: ${{ secrets.S9USER }} - S9PASS: ${{ secrets.S9PASS }} - S9REGISTRY: ${{ secrets.S9REGISTRY }} - run: | - if [[ -z "$S9USER" || -z "$S9PASS" || -z "$S9REGISTRY" ]]; then - echo "Publish skipped: missing registry credentials." - else - start-sdk publish https://$S9USER:$S9PASS@$S9REGISTRY ${{ env.package_id }}.s9pk - fi diff --git a/.gitignore b/.gitignore index 510f988..4dad48c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.s9pk -.vscode/ +startos/*.js +node_modules/ .DS_Store -scripts/*.js +.vscode/ docker-images +javascript +ncc-cache \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..663911b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +## Building and Development + +See the [StartOS Packaging Guide](https://docs.start9.com/packaging-guide/) for complete environment setup and build instructions. + +### Quick Start + +```bash +# Install dependencies +npm ci + +# Build universal package +make +``` diff --git a/Dockerfile b/Dockerfile index 720c9c7..0afde3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,52 +1,6 @@ -# Stage 1: Build -FROM node:18-alpine AS build +FROM germannewsmaker/myspeed:1.0.9 -# Install necessary build dependencies -RUN apk add --no-cache g++ make cmake python3 py3-setuptools git +RUN apk add --no-cache tini -# Set the working directory -WORKDIR /myspeed - -# Clone the MySpeed repository from GitHub -RUN git clone https://github.com/gnmyt/myspeed.git . - -# Install dependencies for the entire application (using npm for compatibility with package-lock.json) -RUN npm install --legacy-peer-deps - -# Build the client application using npm instead of yarn to avoid conflicts -RUN cd client && npm install --legacy-peer-deps -RUN cd client && npm run build - -# Move built client files to the correct location -RUN mv client/build . - -# Stage 2: Production -FROM node:18-alpine - -# Install production dependencies -RUN apk add --no-cache tzdata - -# Set environment variables -ENV NODE_ENV=production -ENV TZ=Etc/UTC - -# Set the working directory -WORKDIR /myspeed - -# Copy the built files and server files from the build stage -COPY --from=build /myspeed/build /myspeed/build -COPY --from=build /myspeed/server /myspeed/server -COPY --from=build /myspeed/node_modules /myspeed/node_modules -COPY --from=build /myspeed/package.json /myspeed/package.json - -# Declare a volume for persistent data -VOLUME ["/myspeed/data"] - -# Expose the necessary port -EXPOSE 5216 - -# Start the application -CMD ["node", "server/server.js"] - -# Add the entrypoint script -ADD ./docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh +ENTRYPOINT ["tini", "--"] +CMD ["node", "server"] diff --git a/Makefile b/Makefile index 65ee8b6..10d4360 100644 --- a/Makefile +++ b/Makefile @@ -1,60 +1,3 @@ -PKG_ID := $(shell yq e ".id" manifest.yaml) -PKG_VERSION := $(shell yq e ".version" manifest.yaml) -TS_FILES := $(shell find ./ -name \*.ts) - -.DELETE_ON_ERROR: - -all: verify - -arm: - @rm -f docker-images/x86_64.tar - @ARCH=aarch64 $(MAKE) - -x86: - @rm -f docker-images/aarch64.tar - @ARCH=x86_64 $(MAKE) - -verify: $(PKG_ID).s9pk - @start-sdk verify s9pk $(PKG_ID).s9pk - @echo " Done!" - @echo " Filesize: $(shell du -h $(PKG_ID).s9pk) is ready" - -install: - @if [ ! -f ~/.embassy/config.yaml ]; then echo "You must define \"host: http://server-name.local\" in ~/.embassy/config.yaml config file first."; exit 1; fi - @echo "\nInstalling to $$(grep -v '^#' ~/.embassy/config.yaml | cut -d'/' -f3) ...\n" - @[ -f $(PKG_ID).s9pk ] || ( $(MAKE) && echo "\nInstalling to $$(grep -v '^#' ~/.embassy/config.yaml | cut -d'/' -f3) ...\n" ) - @start-cli package install $(PKG_ID).s9pk - -clean: - rm -rf docker-images - rm -f $(PKG_ID).s9pk - rm -f scripts/*.js - -scripts/embassy.js: $(TS_FILES) - deno bundle scripts/embassy.ts scripts/embassy.js - -docker-images/aarch64.tar: manifest.yaml Dockerfile docker_entrypoint.sh -ifeq ($(ARCH),x86_64) -else - mkdir -p docker-images - docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) \ - --platform=linux/arm64 -o type=docker,dest=docker-images/aarch64.tar . -endif - -docker-images/x86_64.tar: manifest.yaml Dockerfile docker_entrypoint.sh -ifeq ($(ARCH),aarch64) -else - mkdir -p docker-images - docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) \ - --platform=linux/amd64 -o type=docker,dest=docker-images/x86_64.tar . -endif - -$(PKG_ID).s9pk: manifest.yaml instructions.md icon.png LICENSE scripts/embassy.js docker-images/aarch64.tar docker-images/x86_64.tar scripts/procedures/healthChecks.ts scripts/embassy.ts -ifeq ($(ARCH),aarch64) - @echo "start-sdk: Preparing aarch64 package ..." -else ifeq ($(ARCH),x86_64) - @echo "start-sdk: Preparing x86_64 package ..." -else - @echo "start-sdk: Preparing Universal Package ..." -endif - @start-sdk pack +ARCHES := x86 arm +# overrides to s9pk.mk must precede the include statement +include s9pk.mk diff --git a/README.md b/README.md deleted file mode 100644 index f185c9b..0000000 --- a/README.md +++ /dev/null @@ -1,142 +0,0 @@ -[![Contributors][contributors-shield]][contributors-url] -[![Forks][forks-shield]][forks-url] -[![Stargazers][stars-shield]][stars-url] -[![Issues][issues-shield]][issues-url] -[![MIT License][license-shield]][license-url] -[![Release][release-shield]][release-url] - -
-
- - Logo - -

MySpeed 🇩🇪 🇺🇸

-
- - -## 🤔 What is MySpeed? - -MySpeed is a speed test analysis software that records your internet speed for up to 30 days. - -### ⭐ Features - -- 📊 MySpeed generates clear statistics on speed, ping, and more -- ⏰ MySpeed automates speed tests and allows you to set the time between tests using Cron expressions -- 🗄️ Add multiple servers directly to a MySpeed instance -- 🩺 Configure health checks to notify you via email, Signal, WhatsApp, or Telegram in case of errors or downtime -- 📆 Test results can be stored for up to 30 days -- 🔥 Support for Prometheus and Grafana -- 🗳️ Choose between Ookla, LibreSpeed and Cloudflare speed test servers -- 💁 Learn more about MySpeed on our [website](https://myspeed.dev) - -## Dependencies - -Install the system dependencies below to build this project by following the instructions in the provided links. You can also find detailed steps to setup your environment in the service packaging [documentation](https://docs.start9.com/latest/developer-docs/packaging#development-environment). - -- [docker](https://docs.docker.com/get-docker) -- [docker-buildx](https://docs.docker.com/buildx/working-with-buildx/) -- [yq](https://mikefarah.gitbook.io/yq) -- [deno](https://deno.land/) -- [make](https://www.gnu.org/software/make/) -- [start-sdk](https://github.com/Start9Labs/start-os/tree/sdk) - -## Cloning - -Clone the package repository locally. - -``` -git clone https://github.com/j34g/MySpeed-Start9.git -cd MySpeed-Start9 -``` - -## Building - -To build the service as a universal package, run the following command: - -``` -make -``` - -Alternatively the package can be built for individual architectures by specifying the architecture as follows: - -``` -make x86 -``` - -or - -``` -make arm -``` - -## Installing (on StartOS) - -Before installation, define `host: https://server-name.local` in your `~/.embassy/config.yaml` config file then run the following commands to determine successful install: - -> Change server-name.local to your Start9 server address - -``` -start-cli auth login -#Enter your StartOS password -make install -``` - -**Tip:** You can also install the myspeed.s9pk by sideloading it under the **StartOS > System > Sideload a Service** section. - -## Verify Install - -Go to your StartOS Services page, select **MySpeed** and start the service. - -**Done!** -### 📸 Example Screenshots - -#### Homepage (List View) - -Homepage - -#### Homepage (Statistics View) -Statistics - -#### Server Selection - -Server Selection - -#### Dropdown Menu - -Dropdown Menu - -#### Page During a Speed Test - -Page During a Speed Test - -## Convinced? - -Great, let's get started! You can find the installation instructions for Linux (and Windows) at the top under Installation. - -## License - -Distributed under the MIT license. See `LICENSE` for more information. - -[contributors-shield]: https://img.shields.io/github/contributors/gnmyt/myspeed.svg?style=for-the-badge - -[contributors-url]: https://github.com/gnmyt/myspeed/graphs/contributors - -[forks-shield]: https://img.shields.io/github/forks/gnmyt/myspeed.svg?style=for-the-badge - -[forks-url]: https://github.com/gnmyt/myspeed/network/members - -[stars-shield]: https://img.shields.io/github/stars/gnmyt/myspeed.svg?style=for-the-badge - -[stars-url]: https://github.com/gnmyt/myspeed/stargazers - -[issues-shield]: https://img.shields.io/github/issues/gnmyt/myspeed.svg?style=for-the-badge - -[issues-url]: https://github.com/gnmyt/myspeed/issues - -[license-shield]: https://img.shields.io/github/license/gnmyt/myspeed.svg?style=for-the-badge - -[license-url]: https://github.com/gnmyt/myspeed/blob/master/LICENSE - -[release-shield]: https://img.shields.io/github/v/release/gnmyt/myspeed.svg?style=for-the-badge - -[release-url]: https://github.com/gnmyt/myspeed/releases/latest diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..65526c8 --- /dev/null +++ b/assets/README.md @@ -0,0 +1 @@ +Place additional assets here. diff --git a/docker_entrypoint.sh b/docker_entrypoint.sh deleted file mode 100755 index 4ceae99..0000000 --- a/docker_entrypoint.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -echo -echo "Starting MySpeed..." -echo - -# Set the working directory -WORKDIR="/myspeed" - -# Navigate to the application directory -cd "$WORKDIR" || { echo "Failed to enter directory: $WORKDIR"; exit 1; } - -# Log the contents of the working directory for debugging -echo "Contents of $WORKDIR directory:" -ls -l "$WORKDIR" - -# Check if the server directory exists and start the Node.js server -if [ -d "server" ]; then - echo "Starting the Node.js server in the 'server' directory..." - exec node server -else - echo "Error: server directory does not exist." - exit 1 -fi diff --git a/instructions.md b/instructions.md deleted file mode 100644 index d82d9ba..0000000 --- a/instructions.md +++ /dev/null @@ -1,19 +0,0 @@ -# MySpeed Instructions - -MySpeed is a speed test analysis software that records your internet speed for up to 30 days. - - - -📊 MySpeed generates clear statistics on speed, ping, and more - -⏰ MySpeed automates speed tests and allows you to set the time between tests - -🗄️ Add multiple servers directly to a MySpeed instance - -📆 Test results can be stored for up to 30 days - -🗳️ Choose between Ookla, LibreSpeed and Cloudflare speed test servers (Choose Ookla for the most consistency) - - - -Please visit the linked website for offical insructions: https://docs.myspeed.dev/ diff --git a/manifest.yaml b/manifest.yaml deleted file mode 100644 index 6845bfc..0000000 --- a/manifest.yaml +++ /dev/null @@ -1,94 +0,0 @@ -id: my-speed -title: "MySpeed" -version: 1.0.9 -release-notes: | - * MySpeed First Package -license: MIT -wrapper-repo: "https://github.com/j34g/MySpeed-Start9" -upstream-repo: "https://github.com/gnmyt/myspeed" -support-site: "https://github.com/gnmyt/myspeed/issues" -marketing-site: "https://github.com/gnmyt/myspeed" -donation-url: "https://ko-fi.com/gnmyt" -build: ["make"] -description: - short: Speed test analysis software that tests your internet speed periodically for up to 30 days - long: | - MySpeed is a speed test analysis software that stores the speed of your internet for up to 30 days. This can be useful if you want to know when your network might have drops or if you want to check if your internet matches the booked values from your contract. MySpeed automatically creates tests every hour, which you can view in the dashboard. Tests older than 30 days are automatically deleted to keep the dashboard clean. -assets: - license: LICENSE - icon: icon.png - instructions: instructions.md -main: - type: docker - image: main - entrypoint: "docker_entrypoint.sh" - args: [] - mounts: - main: /app/data - gpu-acceleration: false -hardware-requirements: - arch: - - x86_64 - - aarch64 -health-checks: - web-ui: - name: MySpeed User Interface - success-message: Ready to be visited in a web browser - type: script -config: ~ -properties: ~ -volumes: - main: - type: data -interfaces: - main: - name: User Interface - description: MySpeed Web user interface - tor-config: - port-mapping: - 80: "5216" - lan-config: - 443: - ssl: true - internal: 5216 - ui: true - protocols: - - tcp - - http -dependencies: {} -backup: - create: - type: docker - image: compat - system: true - entrypoint: compat - args: - - duplicity - - create - - /mnt/backup - - /app/data - mounts: - BACKUP: /mnt/backup - main: /app/data - restore: - type: docker - image: compat - system: true - entrypoint: compat - args: - - duplicity - - restore - - /mnt/backup - - /app/data - mounts: - BACKUP: /mnt/backup - main: /app/data -migrations: - from: - "*": - type: script - args: ["from"] - to: - "*": - type: script - args: ["to"] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bb3bb44 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,254 @@ +{ + "name": "myspeed-startos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "myspeed-startos", + "dependencies": { + "@start9labs/start-sdk": "^0.4.0-beta.48" + }, + "devDependencies": { + "@types/node": "^22.19.0", + "@vercel/ncc": "^0.38.4", + "prettier": "^3.6.2", + "typescript": "^5.9.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/@start9labs/start-sdk": { + "version": "0.4.0-beta.48", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.48.tgz", + "integrity": "sha512-beMdwhUffhnbSm3FgkWPJjAWazMhNMzqbHtS6yK2hX6VpP39JxzDVo8vEbJAUfot6LPw+OhOUtPEu6KDaGre6A==", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^3.0.0", + "@noble/curves": "^1.8.2", + "@noble/hashes": "^1.7.2", + "@types/ini": "^4.1.1", + "deep-equality-data-structures": "^2.0.0", + "ini": "^5.0.0", + "isomorphic-fetch": "^3.0.0", + "mime": "^4.0.7", + "ts-matches": "^6.3.2", + "yaml": "^2.7.1" + } + }, + "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.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "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/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/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/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=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/ts-matches": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.5.0.tgz", + "integrity": "sha512-MhuobYhHYn6MlOTPAF/qk3tsRRioPac5ofYn68tc3rAJaGjsw1MsX1MOSep52DkvNJPgNV0F73zfgcQfYTVeyQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "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/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d95700a --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "myspeed-startos", + "scripts": { + "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", + "prettier": "prettier --write startos", + "check": "tsc --noEmit" + }, + "dependencies": { + "@start9labs/start-sdk": "^0.4.0-beta.48" + }, + "devDependencies": { + "@types/node": "^22.19.0", + "@vercel/ncc": "^0.38.4", + "prettier": "^3.6.2", + "typescript": "^5.9.3" + }, + "prettier": { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true + } +} diff --git a/s9pk.mk b/s9pk.mk new file mode 100644 index 0000000..45ebfc9 --- /dev/null +++ b/s9pk.mk @@ -0,0 +1,127 @@ +# ** Plumbing. DO NOT EDIT **. +# This file is imported by ./Makefile. Make edits there + +PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest/index.ts) +INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) +ARCHES ?= x86 arm riscv +TARGETS ?= arches +ifdef VARIANT +BASE_NAME := $(PACKAGE_ID)_$(VARIANT) +else +BASE_NAME := $(PACKAGE_ID) +endif + +.PHONY: all arches aarch64 x86_64 riscv64 arm arm64 x86 riscv arch/* clean install check-deps check-init package ingredients +.DELETE_ON_ERROR: +.SECONDARY: + +define SUMMARY + @manifest=$$(start-cli s9pk inspect $(1) manifest); \ + size=$$(du -h $(1) | awk '{print $$1}'); \ + title=$$(printf '%s' "$$manifest" | jq -r .title); \ + version=$$(printf '%s' "$$manifest" | jq -r .version); \ + arches=$$(printf '%s' "$$manifest" | jq -r '[.images[].arch // []] | flatten | unique | join(", ")'); \ + sdkv=$$(printf '%s' "$$manifest" | jq -r .sdkVersion); \ + gitHash=$$(printf '%s' "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \ + printf "\n"; \ + printf "\033[1;32m✅ Build Complete!\033[0m\n"; \ + printf "\n"; \ + printf "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m\n"; \ + printf "───────────────────────────────\n"; \ + printf " \033[1;36mFilename:\033[0m %s\n" "$(1)"; \ + printf " \033[1;36mSize:\033[0m %s\n" "$$size"; \ + printf " \033[1;36mArch:\033[0m %s\n" "$$arches"; \ + printf " \033[1;36mSDK:\033[0m %s\n" "$$sdkv"; \ + printf " \033[1;36mGit:\033[0m %s\n" "$$gitHash"; \ + echo "" +endef + +all: $(TARGETS) + +arches: $(ARCHES) + +universal: $(BASE_NAME).s9pk + $(call SUMMARY,$<) + +arch/%: $(BASE_NAME)_%.s9pk + $(call SUMMARY,$<) + +x86 x86_64: arch/x86_64 +arm arm64 aarch64: arch/aarch64 +riscv riscv64: arch/riscv64 + +$(BASE_NAME).s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack -o $@ + +$(BASE_NAME)_%.s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack --arch=$* -o $@ + +ingredients: $(INGREDIENTS) + @echo " Re-evaluating ingredients..." + +install: | check-deps check-init + @HOST=$$(awk -F'/' '/^host:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$HOST" ]; then \ + echo "Error: You must define \"host: http://server-name.local\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + S9PK=$$(ls -t *.s9pk 2>/dev/null | head -1); \ + if [ -z "$$S9PK" ]; then \ + echo "Error: No .s9pk file found. Run 'make' first."; \ + exit 1; \ + fi; \ + printf "\n🚀 Installing %s to %s ...\n" "$$S9PK" "$$HOST"; \ + start-cli package install -s "$$S9PK" + +publish: | all + @REGISTRY=$$(awk -F'/' '/^registry:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$REGISTRY" ]; then \ + echo "Error: You must define \"registry: https://my-registry.tld\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + S3BASE=$$(awk -F'/' '/^s9pk-s3base:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$S3BASE" ]; then \ + echo "Error: You must define \"s3base: https://s9pks.my-s3-bucket.tld\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + command -v s3cmd >/dev/null || \ + (echo "Error: s3cmd not found. It must be installed to publish using s3." && exit 1); \ + printf "\n🚀 Publishing to %s; indexing on %s ...\n" "$$S3BASE" "$$REGISTRY"; \ + for s9pk in *.s9pk; do \ + age=$$(( $$(date +%s) - $$(stat -c %Y "$$s9pk") )); \ + if [ "$$age" -gt 3600 ]; then \ + printf "\033[1;33m⚠️ %s is %d minutes old. Publish anyway? [y/N] \033[0m" "$$s9pk" "$$((age / 60))"; \ + read -r ans; \ + case "$$ans" in [yY]*) ;; *) echo "Skipping $$s9pk"; continue ;; esac; \ + fi; \ + start-cli s9pk publish "$$s9pk"; \ + done + +check-deps: + @command -v start-cli >/dev/null || \ + (echo "Error: start-cli not found. Please see https://docs.start9.com/latest/developer-guide/sdk/installing-the-sdk" && exit 1) + @command -v npm >/dev/null || \ + (echo "Error: npm not found. Please install Node.js and npm." && exit 1) + +check-init: + @if [ ! -f ~/.startos/developer.key.pem ]; then \ + echo "Initializing StartOS developer environment..."; \ + start-cli init-key; \ + fi + +javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules + npm run build + +node_modules: package-lock.json + npm ci + +package-lock.json: package.json + npm i + +clean: + @echo "Cleaning up build artifacts..." + @rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk $(PACKAGE_ID)_riscv64.s9pk javascript node_modules diff --git a/scripts/deps.ts b/scripts/deps.ts deleted file mode 100644 index 3105b54..0000000 --- a/scripts/deps.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "https://deno.land/x/embassyd_sdk@v0.3.3.0.11/mod.ts"; diff --git a/scripts/embassy.js b/scripts/embassy.js deleted file mode 100644 index 21eff07..0000000 --- a/scripts/embassy.js +++ /dev/null @@ -1,4197 +0,0 @@ -// deno-fmt-ignore-file -// deno-lint-ignore-file -// This code was bundled using `deno bundle` and it's not recommended to edit it manually - -function saferStringify(x) { - try { - return JSON.stringify(x); - } catch (e) { - return "" + x; - } -} -class AnyParser { - description; - constructor(description = { - name: "Any", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - return onParse.parsed(a); - } -} -class ArrayParser { - description; - constructor(description = { - name: "Array", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (Array.isArray(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class BoolParser { - description; - constructor(description = { - name: "Boolean", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (a === true || a === false) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -const isObject = (x)=>typeof x === "object" && x != null; -const isFunctionTest = (x)=>typeof x === "function"; -const isNumber = (x)=>typeof x === "number"; -const isString = (x)=>typeof x === "string"; -const booleanOnParse = { - parsed (_) { - return true; - }, - invalid (_) { - return false; - } -}; -class FunctionParser { - description; - constructor(description = { - name: "Function", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isFunctionTest(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class NilParser { - description; - constructor(description = { - name: "Null", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (a === null || a === undefined) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class ObjectParser { - description; - constructor(description = { - name: "Object", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isObject(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class StringParser { - description; - constructor(description = { - name: "String", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isString(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class UnknownParser { - description; - constructor(description = { - name: "Unknown", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - return onParse.parsed(a); - } -} -class ConcatParsers { - parent; - otherParser; - description; - constructor(parent, otherParser, description = { - name: "Concat", - children: [ - parent, - otherParser - ], - extras: [] - }){ - this.parent = parent; - this.otherParser = otherParser; - this.description = description; - } - static of(parent, otherParser) { - if (parent.unwrappedParser().description.name === "Any") { - return otherParser; - } - if (otherParser.unwrappedParser().description.name === "Any") { - return parent; - } - return new ConcatParsers(parent, otherParser); - } - parse(a, onParse) { - const parent = this.parent.enumParsed(a); - if ("error" in parent) { - return onParse.invalid(parent.error); - } - const other = this.otherParser.enumParsed(parent.value); - if ("error" in other) { - return onParse.invalid(other.error); - } - return onParse.parsed(other.value); - } -} -class DefaultParser { - parent; - defaultValue; - description; - constructor(parent, defaultValue, description = { - name: "Default", - children: [ - parent - ], - extras: [ - defaultValue - ] - }){ - this.parent = parent; - this.defaultValue = defaultValue; - this.description = description; - } - parse(a, onParse) { - const parser = this; - const defaultValue = this.defaultValue; - if (a == null) { - return onParse.parsed(defaultValue); - } - const parentCheck = this.parent.enumParsed(a); - if ("error" in parentCheck) { - parentCheck.error.parser = parser; - return onParse.invalid(parentCheck.error); - } - return onParse.parsed(parentCheck.value); - } -} -class GuardParser { - checkIsA; - typeName; - description; - constructor(checkIsA, typeName, description = { - name: "Guard", - children: [], - extras: [ - typeName - ] - }){ - this.checkIsA = checkIsA; - this.typeName = typeName; - this.description = description; - } - parse(a, onParse) { - if (this.checkIsA(a)) { - return onParse.parsed(a); - } - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -class MappedAParser { - parent; - map; - mappingName; - description; - constructor(parent, map, mappingName = map.name, description = { - name: "Mapped", - children: [ - parent - ], - extras: [ - mappingName - ] - }){ - this.parent = parent; - this.map = map; - this.mappingName = mappingName; - this.description = description; - } - parse(a, onParse) { - const map = this.map; - const result = this.parent.enumParsed(a); - if ("error" in result) { - return onParse.invalid(result.error); - } - return onParse.parsed(map(result.value)); - } -} -class MaybeParser { - parent; - description; - constructor(parent, description = { - name: "Maybe", - children: [ - parent - ], - extras: [] - }){ - this.parent = parent; - this.description = description; - } - parse(a, onParse) { - if (a == null) { - return onParse.parsed(null); - } - const parser = this; - const parentState = this.parent.enumParsed(a); - if ("error" in parentState) { - const { error } = parentState; - error.parser = parser; - return onParse.invalid(error); - } - return onParse.parsed(parentState.value); - } -} -class OrParsers { - parent; - otherParser; - description; - constructor(parent, otherParser, description = { - name: "Or", - children: [ - parent, - otherParser - ], - extras: [] - }){ - this.parent = parent; - this.otherParser = otherParser; - this.description = description; - } - parse(a, onParse) { - const parser = this; - const parent = this.parent.enumParsed(a); - if ("value" in parent) { - return onParse.parsed(parent.value); - } - const other = this.otherParser.enumParsed(a); - if ("error" in other) { - const { error } = other; - error.parser = parser; - return onParse.invalid(error); - } - return onParse.parsed(other.value); - } -} -class NumberParser { - description; - constructor(description = { - name: "Number", - children: [], - extras: [] - }){ - this.description = description; - } - parse(a, onParse) { - if (isNumber(a)) return onParse.parsed(a); - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -function unwrapParser(a) { - if (a instanceof Parser) return unwrapParser(a.parser); - return a; -} -const enumParsed = { - parsed (value) { - return { - value - }; - }, - invalid (error) { - return { - error - }; - } -}; -class Parser { - parser; - description; - _TYPE; - constructor(parser, description = { - name: "Wrapper", - children: [ - parser - ], - extras: [] - }){ - this.parser = parser; - this.description = description; - this._TYPE = null; - this.parse = (a, onParse)=>{ - return this.parser.parse(a, onParse); - }; - this.unsafeCast = (value)=>{ - const state = this.enumParsed(value); - if ("value" in state) return state.value; - const { error } = state; - throw new TypeError(`Failed type: ${Parser.validatorErrorAsString(error)} given input ${saferStringify(value)}`); - }; - this.castPromise = (value)=>{ - const state = this.enumParsed(value); - if ("value" in state) return Promise.resolve(state.value); - const { error } = state; - return Promise.reject(new TypeError(`Failed type: ${Parser.validatorErrorAsString(error)} given input ${saferStringify(value)}`)); - }; - this.errorMessage = (input)=>{ - const parsed = this.parse(input, enumParsed); - if ("value" in parsed) return; - return Parser.validatorErrorAsString(parsed.error); - }; - this.map = (fn, mappingName)=>{ - return new Parser(new MappedAParser(this, fn, mappingName)); - }; - this.concat = (otherParser)=>{ - return new Parser(ConcatParsers.of(this, new Parser(otherParser))); - }; - this.orParser = (otherParser)=>{ - return new Parser(new OrParsers(this, new Parser(otherParser))); - }; - this.test = (value)=>{ - return this.parse(value, booleanOnParse); - }; - this.optional = (_name)=>{ - return new Parser(new MaybeParser(this)); - }; - this.defaultTo = (defaultValue)=>{ - return new Parser(new DefaultParser(new Parser(new MaybeParser(this)), defaultValue)); - }; - this.validate = (isValid, otherName)=>{ - return new Parser(ConcatParsers.of(this, new Parser(new GuardParser(isValid, otherName)))); - }; - this.refine = (refinementTest, otherName = refinementTest.name)=>{ - return new Parser(ConcatParsers.of(this, new Parser(new GuardParser(refinementTest, otherName)))); - }; - this.rename = (nameString)=>{ - return parserName(nameString, this); - }; - this.enumParsed = (value)=>{ - return this.parse(value, enumParsed); - }; - this.unwrappedParser = ()=>{ - let answer = this; - while(true){ - const next = answer.parser; - if (next instanceof Parser) { - answer = next; - } else { - return next; - } - } - }; - } - parse; - static isA(checkIsA, name) { - return new Parser(new GuardParser(checkIsA, name)); - } - static validatorErrorAsString = (error)=>{ - const { parser, value, keys } = error; - const keysString = !keys.length ? "" : keys.map((x)=>`[${x}]`).reverse().join(""); - return `${keysString}${Parser.parserAsString(parser)}(${saferStringify(value)})`; - }; - static parserAsString(parserComingIn) { - const parser = unwrapParser(parserComingIn); - const { description: { name, extras, children } } = parser; - if (parser instanceof ShapeParser) { - return `${name}<{${parser.description.children.map((subParser, i)=>`${String(parser.description.extras[i]) || "?"}:${Parser.parserAsString(subParser)}`).join(",")}}>`; - } - if (parser instanceof OrParsers) { - const parent = unwrapParser(parser.parent); - const parentString = Parser.parserAsString(parent); - if (parent instanceof OrParsers) return parentString; - return `${name}<${parentString},...>`; - } - if (parser instanceof GuardParser) { - return String(extras[0] || name); - } - if (parser instanceof StringParser || parser instanceof ObjectParser || parser instanceof NumberParser || parser instanceof BoolParser || parser instanceof AnyParser) { - return name.toLowerCase(); - } - if (parser instanceof FunctionParser) { - return name; - } - if (parser instanceof NilParser) { - return "null"; - } - if (parser instanceof ArrayParser) { - return "Array"; - } - const specifiers = [ - ...extras.map(saferStringify), - ...children.map(Parser.parserAsString) - ]; - const specifiersString = `<${specifiers.join(",")}>`; - return `${name}${specifiersString}`; - } - unsafeCast; - castPromise; - errorMessage; - map; - concat; - orParser; - test; - optional; - defaultTo; - validate; - refine; - rename; - enumParsed; - unwrappedParser; -} -function guard(test, testName) { - return Parser.isA(test, testName || test.name); -} -const any = new Parser(new AnyParser()); -class ArrayOfParser { - parser; - description; - constructor(parser, description = { - name: "ArrayOf", - children: [ - parser - ], - extras: [] - }){ - this.parser = parser; - this.description = description; - } - parse(a, onParse) { - if (!Array.isArray(a)) { - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } - const values = [ - ...a - ]; - for(let index = 0; index < values.length; index++){ - const result = this.parser.enumParsed(values[index]); - if ("error" in result) { - result.error.keys.push("" + index); - return onParse.invalid(result.error); - } else { - values[index] = result.value; - } - } - return onParse.parsed(values); - } -} -function arrayOf(validator) { - return new Parser(new ArrayOfParser(validator)); -} -const unknown = new Parser(new UnknownParser()); -const number = new Parser(new NumberParser()); -const isNill = new Parser(new NilParser()); -const natural = number.refine((x)=>x >= 0 && x === Math.floor(x)); -const isFunction = new Parser(new FunctionParser()); -const __boolean = new Parser(new BoolParser()); -class DeferredParser { - description; - parser; - static create() { - return new DeferredParser(); - } - constructor(description = { - name: "Deferred", - children: [], - extras: [] - }){ - this.description = description; - } - setParser(parser) { - this.parser = new Parser(parser); - return this; - } - parse(a, onParse) { - if (!this.parser) { - return onParse.invalid({ - value: "Not Set Up", - keys: [], - parser: this - }); - } - return this.parser.parse(a, onParse); - } -} -function deferred() { - const deferred = DeferredParser.create(); - function setParser(parser) { - deferred.setParser(parser); - } - return [ - new Parser(deferred), - setParser - ]; -} -function every(...parsers) { - const filteredParsers = parsers.filter((x)=>x !== any); - if (filteredParsers.length <= 0) { - return any; - } - const first = filteredParsers.splice(0, 1)[0]; - return filteredParsers.reduce((left, right)=>{ - return left.concat(right); - }, first); -} -class ShapeParser { - parserMap; - isPartial; - parserKeys; - description; - constructor(parserMap, isPartial, parserKeys = Object.keys(parserMap), description = { - name: isPartial ? "Partial" : "Shape", - children: parserKeys.map((key)=>parserMap[key]), - extras: parserKeys - }){ - this.parserMap = parserMap; - this.isPartial = isPartial; - this.parserKeys = parserKeys; - this.description = description; - } - parse(a, onParse) { - const parser = this; - if (!object.test(a)) { - return onParse.invalid({ - value: a, - keys: [], - parser - }); - } - const { parserMap, isPartial } = this; - const value = { - ...a - }; - if (Array.isArray(a)) { - value.length = a.length; - } - for(const key in parserMap){ - if (key in value) { - const parser = parserMap[key]; - const state = parser.enumParsed(a[key]); - if ("error" in state) { - const { error } = state; - error.keys.push(saferStringify(key)); - return onParse.invalid(error); - } - const smallValue = state.value; - value[key] = smallValue; - } else if (!isPartial) { - return onParse.invalid({ - value: "missingProperty", - parser, - keys: [ - saferStringify(key) - ] - }); - } - } - return onParse.parsed(value); - } -} -const isPartial = (testShape)=>{ - return new Parser(new ShapeParser(testShape, true)); -}; -const partial = isPartial; -const isShape = (testShape)=>{ - return new Parser(new ShapeParser(testShape, false)); -}; -function shape(testShape, optionals, optionalAndDefaults) { - if (optionals) { - const defaults = optionalAndDefaults || {}; - const entries = Object.entries(testShape); - const optionalSet = new Set(Array.from(optionals)); - return every(partial(Object.fromEntries(entries.filter(([key, _])=>optionalSet.has(key)).map(([key, parser])=>[ - key, - parser.optional() - ]))), isShape(Object.fromEntries(entries.filter(([key, _])=>!optionalSet.has(key))))).map((ret)=>{ - for (const key of optionalSet){ - const keyAny = key; - if (!(keyAny in ret) && keyAny in defaults) { - ret[keyAny] = defaults[keyAny]; - } - } - return ret; - }); - } - return isShape(testShape); -} -const objectMatcher = new Parser(new ObjectParser()); -const object = Object.assign(function objectOf(...args) { - return shape(...args); -}, objectMatcher); -class DictionaryParser { - parsers; - description; - constructor(parsers, description = { - name: "Dictionary", - children: parsers.reduce((acc, [k, v])=>{ - acc.push(k, v); - return acc; - }, []), - extras: [] - }){ - this.parsers = parsers; - this.description = description; - } - parse(a, onParse) { - const { parsers } = this; - const parser = this; - const entries = Object.entries(a); - for (const entry of entries){ - const [key, value] = entry; - const found = findOrError(parsers, key, value, parser); - if (found == undefined) return onParse.parsed(a); - if ("error" in found) return onParse.invalid(found.error); - entry[0] = found[0].value; - entry[1] = found[1].value; - } - const answer = Object.fromEntries(entries); - return onParse.parsed(answer); - } -} -const dictionary = (...parsers)=>{ - return object.concat(new DictionaryParser([ - ...parsers - ])); -}; -const isArray = new Parser(new ArrayParser()); -const string = new Parser(new StringParser()); -const instanceOf = (classCreator)=>guard((x)=>x instanceof classCreator, `is${classCreator.name}`); -class LiteralsParser { - values; - description; - constructor(values, description = { - name: "Literal", - children: [], - extras: values - }){ - this.values = values; - this.description = description; - } - parse(a, onParse) { - if (this.values.indexOf(a) >= 0) { - return onParse.parsed(a); - } - return onParse.invalid({ - value: a, - keys: [], - parser: this - }); - } -} -function literal(isEqualToValue) { - return new Parser(new LiteralsParser([ - isEqualToValue - ])); -} -function literals(firstValue, ...restValues) { - return new Parser(new LiteralsParser([ - firstValue, - ...restValues - ])); -} -class RecursiveParser { - recursive; - description; - parser; - static create(fn) { - const parser = new RecursiveParser(fn); - parser.parser = fn(new Parser(parser)); - return parser; - } - constructor(recursive, description = { - name: "Recursive", - children: [], - extras: [ - recursive - ] - }){ - this.recursive = recursive; - this.description = description; - } - parse(a, onParse) { - if (!this.parser) { - return onParse.invalid({ - value: "Recursive Invalid State", - keys: [], - parser: this - }); - } - return this.parser.parse(a, onParse); - } -} -function recursive(fn) { - fn(any); - const created = RecursiveParser.create(fn); - return new Parser(created); -} -const regex = (tester)=>string.refine(function(x) { - return tester.test(x); - }, tester.toString()); -function some(...parsers) { - if (parsers.length <= 0) { - return any; - } - const first = parsers.splice(0, 1)[0]; - return parsers.reduce((left, right)=>left.orParser(right), first); -} -class TupleParser { - parsers; - lengthMatcher; - description; - constructor(parsers, lengthMatcher = literal(parsers.length), description = { - name: "Tuple", - children: parsers, - extras: [] - }){ - this.parsers = parsers; - this.lengthMatcher = lengthMatcher; - this.description = description; - } - parse(input, onParse) { - const tupleError = isArray.enumParsed(input); - if ("error" in tupleError) return onParse.invalid(tupleError.error); - const values = input; - const stateCheck = this.lengthMatcher.enumParsed(values.length); - if ("error" in stateCheck) { - stateCheck.error.keys.push(saferStringify("length")); - return onParse.invalid(stateCheck.error); - } - const answer = new Array(this.parsers.length); - for(const key in this.parsers){ - const parser = this.parsers[key]; - const value = values[key]; - const result = parser.enumParsed(value); - if ("error" in result) { - const { error } = result; - error.keys.push(saferStringify(key)); - return onParse.invalid(error); - } - answer[key] = result.value; - } - return onParse.parsed(answer); - } -} -function tuple(...parsers) { - return new Parser(new TupleParser(parsers)); -} -class NamedParser { - parent; - name; - description; - constructor(parent, name, description = { - name: "Named", - children: [ - parent - ], - extras: [ - name - ] - }){ - this.parent = parent; - this.name = name; - this.description = description; - } - parse(a, onParse) { - const parser = this; - const parent = this.parent.enumParsed(a); - if ("error" in parent) { - const { error } = parent; - error.parser = parser; - return onParse.invalid(error); - } - return onParse.parsed(parent.value); - } -} -function findOrError(parsers, key, value, parser) { - let foundError; - for (const [keyParser, valueParser] of parsers){ - const enumState = keyParser.enumParsed(key); - const valueState = valueParser.enumParsed(value); - if ("error" in enumState) { - if (!foundError) { - const { error } = enumState; - error.parser = parser; - error.keys.push("" + key); - foundError = { - error - }; - } - continue; - } - const newKey = enumState.value; - if ("error" in valueState) { - if (!foundError) { - const { error } = valueState; - error.keys.push("" + newKey); - foundError = { - error - }; - } - continue; - } - return [ - enumState, - valueState - ]; - } - return foundError; -} -function parserName(name, parent) { - return new Parser(new NamedParser(parent, name)); -} -class Matched { - value; - constructor(value){ - this.value = value; - this.when = (..._args)=>{ - return this; - }; - this.unwrap = ()=>{ - return this.value; - }; - } - when; - defaultTo(_defaultValue) { - return this.value; - } - defaultToLazy(_getValue) { - return this.value; - } - unwrap; -} -class MatchMore { - a; - constructor(a){ - this.a = a; - this.when = (...args)=>{ - const [outcome, ...matchers] = args.reverse(); - const me = this; - const parser = matches.some(...matchers.map((matcher)=>matcher instanceof Parser ? matcher : literal(matcher))); - const result = parser.enumParsed(this.a); - if ("error" in result) { - return me; - } - const { value } = result; - if (outcome instanceof Function) { - return new Matched(outcome(value)); - } - return new Matched(outcome); - }; - this.unwrap = ()=>{ - throw new Error("Expecting that value is matched"); - }; - } - when; - defaultTo(value) { - return value; - } - defaultToLazy(getValue) { - return getValue(); - } - unwrap; -} -const array = Object.assign(function arrayOfWrapper(...args) { - return arrayOf(...args); -}, isArray); -const matches = Object.assign(function matchesFn(value) { - return new MatchMore(value); -}, { - array, - arrayOf, - some, - tuple, - regex, - number, - natural, - isFunction, - object, - string, - shape, - partial, - literal, - every, - guard, - unknown, - any, - boolean: __boolean, - dictionary, - literals, - nill: isNill, - instanceOf, - Parse: Parser, - parserName, - recursive, - deferred -}); -const mod = { - AnyParser: AnyParser, - ArrayParser: ArrayParser, - BoolParser: BoolParser, - FunctionParser: FunctionParser, - GuardParser: GuardParser, - NilParser: NilParser, - NumberParser: NumberParser, - ObjectParser: ObjectParser, - OrParsers: OrParsers, - ShapeParser: ShapeParser, - StringParser: StringParser, - saferStringify: saferStringify, - NamedParser: NamedParser, - ArrayOfParser: ArrayOfParser, - LiteralsParser: LiteralsParser, - ConcatParsers: ConcatParsers, - MappedAParser: MappedAParser, - default: matches, - Validator: Parser, - matches, - allOf: every, - any, - anyOf: some, - array, - arrayOf, - boolean: __boolean, - deferred, - dictionary, - every, - guard, - instanceOf, - isFunction, - literal, - literals, - natural, - nill: isNill, - number, - object, - oneOf: some, - Parse: Parser, - Parser, - parserName, - partial, - recursive, - regex, - shape, - some, - string, - tuple, - unknown -}; -class YAMLError extends Error { - mark; - constructor(message = "(unknown reason)", mark = ""){ - super(`${message} ${mark}`); - this.mark = mark; - this.name = this.constructor.name; - } - toString(_compact) { - return `${this.name}: ${this.message} ${this.mark}`; - } -} -function isBoolean(value) { - return typeof value === "boolean" || value instanceof Boolean; -} -function isObject1(value) { - return value !== null && typeof value === "object"; -} -function repeat(str, count) { - let result = ""; - for(let cycle = 0; cycle < count; cycle++){ - result += str; - } - return result; -} -function isNegativeZero(i) { - return i === 0 && Number.NEGATIVE_INFINITY === 1 / i; -} -class Mark { - name; - buffer; - position; - line; - column; - constructor(name, buffer, position, line, column){ - this.name = name; - this.buffer = buffer; - this.position = position; - this.line = line; - this.column = column; - } - getSnippet(indent = 4, maxLength = 75) { - if (!this.buffer) return null; - let head = ""; - let start = this.position; - while(start > 0 && "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(start - 1)) === -1){ - start -= 1; - if (this.position - start > maxLength / 2 - 1) { - head = " ... "; - start += 5; - break; - } - } - let tail = ""; - let end = this.position; - while(end < this.buffer.length && "\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(end)) === -1){ - end += 1; - if (end - this.position > maxLength / 2 - 1) { - tail = " ... "; - end -= 5; - break; - } - } - const snippet = this.buffer.slice(start, end); - return `${repeat(" ", indent)}${head}${snippet}${tail}\n${repeat(" ", indent + this.position - start + head.length)}^`; - } - toString(compact) { - let snippet, where = ""; - if (this.name) { - where += `in "${this.name}" `; - } - where += `at line ${this.line + 1}, column ${this.column + 1}`; - if (!compact) { - snippet = this.getSnippet(); - if (snippet) { - where += `:\n${snippet}`; - } - } - return where; - } -} -function compileList(schema, name, result) { - const exclude = []; - for (const includedSchema of schema.include){ - result = compileList(includedSchema, name, result); - } - for (const currentType of schema[name]){ - for(let previousIndex = 0; previousIndex < result.length; previousIndex++){ - const previousType = result[previousIndex]; - if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) { - exclude.push(previousIndex); - } - } - result.push(currentType); - } - return result.filter((_type, index)=>!exclude.includes(index)); -} -function compileMap(...typesList) { - const result = { - fallback: {}, - mapping: {}, - scalar: {}, - sequence: {} - }; - for (const types of typesList){ - for (const type of types){ - if (type.kind !== null) { - result[type.kind][type.tag] = result["fallback"][type.tag] = type; - } - } - } - return result; -} -class Schema { - static SCHEMA_DEFAULT; - implicit; - explicit; - include; - compiledImplicit; - compiledExplicit; - compiledTypeMap; - constructor(definition){ - this.explicit = definition.explicit || []; - this.implicit = definition.implicit || []; - this.include = definition.include || []; - for (const type of this.implicit){ - if (type.loadKind && type.loadKind !== "scalar") { - throw new YAMLError("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."); - } - } - this.compiledImplicit = compileList(this, "implicit", []); - this.compiledExplicit = compileList(this, "explicit", []); - this.compiledTypeMap = compileMap(this.compiledImplicit, this.compiledExplicit); - } - extend(definition) { - return new Schema({ - implicit: [ - ...new Set([ - ...this.implicit, - ...definition?.implicit ?? [] - ]) - ], - explicit: [ - ...new Set([ - ...this.explicit, - ...definition?.explicit ?? [] - ]) - ], - include: [ - ...new Set([ - ...this.include, - ...definition?.include ?? [] - ]) - ] - }); - } - static create() {} -} -const DEFAULT_RESOLVE = ()=>true; -const DEFAULT_CONSTRUCT = (data)=>data; -function checkTagFormat(tag) { - return tag; -} -class Type { - tag; - kind = null; - instanceOf; - predicate; - represent; - defaultStyle; - styleAliases; - loadKind; - constructor(tag, options){ - this.tag = checkTagFormat(tag); - if (options) { - this.kind = options.kind; - this.resolve = options.resolve || DEFAULT_RESOLVE; - this.construct = options.construct || DEFAULT_CONSTRUCT; - this.instanceOf = options.instanceOf; - this.predicate = options.predicate; - this.represent = options.represent; - this.defaultStyle = options.defaultStyle; - this.styleAliases = options.styleAliases; - } - } - resolve = ()=>true; - construct = (data)=>data; -} -class DenoStdInternalError extends Error { - constructor(message){ - super(message); - this.name = "DenoStdInternalError"; - } -} -function assert(expr, msg = "") { - if (!expr) { - throw new DenoStdInternalError(msg); - } -} -function copy(src, dst, off = 0) { - off = Math.max(0, Math.min(off, dst.byteLength)); - const dstBytesAvailable = dst.byteLength - off; - if (src.byteLength > dstBytesAvailable) { - src = src.subarray(0, dstBytesAvailable); - } - dst.set(src, off); - return src.byteLength; -} -const MIN_READ = 32 * 1024; -const MAX_SIZE = 2 ** 32 - 2; -class Buffer { - #buf; - #off = 0; - constructor(ab){ - this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); - } - bytes(options = { - copy: true - }) { - if (options.copy === false) return this.#buf.subarray(this.#off); - return this.#buf.slice(this.#off); - } - empty() { - return this.#buf.byteLength <= this.#off; - } - get length() { - return this.#buf.byteLength - this.#off; - } - get capacity() { - return this.#buf.buffer.byteLength; - } - truncate(n) { - if (n === 0) { - this.reset(); - return; - } - if (n < 0 || n > this.length) { - throw Error("bytes.Buffer: truncation out of range"); - } - this.#reslice(this.#off + n); - } - reset() { - this.#reslice(0); - this.#off = 0; - } - #tryGrowByReslice(n) { - const l = this.#buf.byteLength; - if (n <= this.capacity - l) { - this.#reslice(l + n); - return l; - } - return -1; - } - #reslice(len) { - assert(len <= this.#buf.buffer.byteLength); - this.#buf = new Uint8Array(this.#buf.buffer, 0, len); - } - readSync(p) { - if (this.empty()) { - this.reset(); - if (p.byteLength === 0) { - return 0; - } - return null; - } - const nread = copy(this.#buf.subarray(this.#off), p); - this.#off += nread; - return nread; - } - read(p) { - const rr = this.readSync(p); - return Promise.resolve(rr); - } - writeSync(p) { - const m = this.#grow(p.byteLength); - return copy(p, this.#buf, m); - } - write(p) { - const n = this.writeSync(p); - return Promise.resolve(n); - } - #grow(n) { - const m = this.length; - if (m === 0 && this.#off !== 0) { - this.reset(); - } - const i = this.#tryGrowByReslice(n); - if (i >= 0) { - return i; - } - const c = this.capacity; - if (n <= Math.floor(c / 2) - m) { - copy(this.#buf.subarray(this.#off), this.#buf); - } else if (c + n > MAX_SIZE) { - throw new Error("The buffer cannot be grown beyond the maximum size."); - } else { - const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); - copy(this.#buf.subarray(this.#off), buf); - this.#buf = buf; - } - this.#off = 0; - this.#reslice(Math.min(m + n, MAX_SIZE)); - return m; - } - grow(n) { - if (n < 0) { - throw Error("Buffer.grow: negative count"); - } - const m = this.#grow(n); - this.#reslice(m); - } - async readFrom(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while(true){ - const shouldGrow = this.capacity - this.length < MIN_READ; - const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length); - const nread = await r.read(buf); - if (nread === null) { - return n; - } - if (shouldGrow) this.writeSync(buf.subarray(0, nread)); - else this.#reslice(this.length + nread); - n += nread; - } - } - readFromSync(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while(true){ - const shouldGrow = this.capacity - this.length < MIN_READ; - const buf = shouldGrow ? tmp : new Uint8Array(this.#buf.buffer, this.length); - const nread = r.readSync(buf); - if (nread === null) { - return n; - } - if (shouldGrow) this.writeSync(buf.subarray(0, nread)); - else this.#reslice(this.length + nread); - n += nread; - } - } -} -const BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r"; -function resolveYamlBinary(data) { - if (data === null) return false; - let code; - let bitlen = 0; - const max = data.length; - const map = BASE64_MAP; - for(let idx = 0; idx < max; idx++){ - code = map.indexOf(data.charAt(idx)); - if (code > 64) continue; - if (code < 0) return false; - bitlen += 6; - } - return bitlen % 8 === 0; -} -function constructYamlBinary(data) { - const input = data.replace(/[\r\n=]/g, ""); - const max = input.length; - const map = BASE64_MAP; - const result = []; - let bits = 0; - for(let idx = 0; idx < max; idx++){ - if (idx % 4 === 0 && idx) { - result.push(bits >> 16 & 0xff); - result.push(bits >> 8 & 0xff); - result.push(bits & 0xff); - } - bits = bits << 6 | map.indexOf(input.charAt(idx)); - } - const tailbits = max % 4 * 6; - if (tailbits === 0) { - result.push(bits >> 16 & 0xff); - result.push(bits >> 8 & 0xff); - result.push(bits & 0xff); - } else if (tailbits === 18) { - result.push(bits >> 10 & 0xff); - result.push(bits >> 2 & 0xff); - } else if (tailbits === 12) { - result.push(bits >> 4 & 0xff); - } - return new Buffer(new Uint8Array(result)); -} -function representYamlBinary(object) { - const max = object.length; - const map = BASE64_MAP; - let result = ""; - let bits = 0; - for(let idx = 0; idx < max; idx++){ - if (idx % 3 === 0 && idx) { - result += map[bits >> 18 & 0x3f]; - result += map[bits >> 12 & 0x3f]; - result += map[bits >> 6 & 0x3f]; - result += map[bits & 0x3f]; - } - bits = (bits << 8) + object[idx]; - } - const tail = max % 3; - if (tail === 0) { - result += map[bits >> 18 & 0x3f]; - result += map[bits >> 12 & 0x3f]; - result += map[bits >> 6 & 0x3f]; - result += map[bits & 0x3f]; - } else if (tail === 2) { - result += map[bits >> 10 & 0x3f]; - result += map[bits >> 4 & 0x3f]; - result += map[bits << 2 & 0x3f]; - result += map[64]; - } else if (tail === 1) { - result += map[bits >> 2 & 0x3f]; - result += map[bits << 4 & 0x3f]; - result += map[64]; - result += map[64]; - } - return result; -} -function isBinary(obj) { - if (typeof obj?.readSync !== "function") { - return false; - } - const buf = new Buffer(); - try { - if (0 > buf.readFromSync(obj)) return true; - return false; - } catch { - return false; - } finally{ - buf.reset(); - } -} -const binary = new Type("tag:yaml.org,2002:binary", { - construct: constructYamlBinary, - kind: "scalar", - predicate: isBinary, - represent: representYamlBinary, - resolve: resolveYamlBinary -}); -function resolveYamlBoolean(data) { - const max = data.length; - return max === 4 && (data === "true" || data === "True" || data === "TRUE") || max === 5 && (data === "false" || data === "False" || data === "FALSE"); -} -function constructYamlBoolean(data) { - return data === "true" || data === "True" || data === "TRUE"; -} -const bool = new Type("tag:yaml.org,2002:bool", { - construct: constructYamlBoolean, - defaultStyle: "lowercase", - kind: "scalar", - predicate: isBoolean, - represent: { - lowercase (object) { - return object ? "true" : "false"; - }, - uppercase (object) { - return object ? "TRUE" : "FALSE"; - }, - camelcase (object) { - return object ? "True" : "False"; - } - }, - resolve: resolveYamlBoolean -}); -const YAML_FLOAT_PATTERN = new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?" + "|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?" + "|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*" + "|[-+]?\\.(?:inf|Inf|INF)" + "|\\.(?:nan|NaN|NAN))$"); -function resolveYamlFloat(data) { - if (!YAML_FLOAT_PATTERN.test(data) || data[data.length - 1] === "_") { - return false; - } - return true; -} -function constructYamlFloat(data) { - let value = data.replace(/_/g, "").toLowerCase(); - const sign = value[0] === "-" ? -1 : 1; - const digits = []; - if ("+-".indexOf(value[0]) >= 0) { - value = value.slice(1); - } - if (value === ".inf") { - return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; - } - if (value === ".nan") { - return NaN; - } - if (value.indexOf(":") >= 0) { - value.split(":").forEach((v)=>{ - digits.unshift(parseFloat(v)); - }); - let valueNb = 0.0; - let base = 1; - digits.forEach((d)=>{ - valueNb += d * base; - base *= 60; - }); - return sign * valueNb; - } - return sign * parseFloat(value); -} -const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; -function representYamlFloat(object, style) { - if (isNaN(object)) { - switch(style){ - case "lowercase": - return ".nan"; - case "uppercase": - return ".NAN"; - case "camelcase": - return ".NaN"; - } - } else if (Number.POSITIVE_INFINITY === object) { - switch(style){ - case "lowercase": - return ".inf"; - case "uppercase": - return ".INF"; - case "camelcase": - return ".Inf"; - } - } else if (Number.NEGATIVE_INFINITY === object) { - switch(style){ - case "lowercase": - return "-.inf"; - case "uppercase": - return "-.INF"; - case "camelcase": - return "-.Inf"; - } - } else if (isNegativeZero(object)) { - return "-0.0"; - } - const res = object.toString(10); - return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res; -} -function isFloat(object) { - return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 !== 0 || isNegativeZero(object)); -} -const __float = new Type("tag:yaml.org,2002:float", { - construct: constructYamlFloat, - defaultStyle: "lowercase", - kind: "scalar", - predicate: isFloat, - represent: representYamlFloat, - resolve: resolveYamlFloat -}); -function reconstructFunction(code) { - const func = new Function(`return ${code}`)(); - if (!(func instanceof Function)) { - throw new TypeError(`Expected function but got ${typeof func}: ${code}`); - } - return func; -} -new Type("tag:yaml.org,2002:js/function", { - kind: "scalar", - resolve (data) { - if (data === null) { - return false; - } - try { - reconstructFunction(`${data}`); - return true; - } catch (_err) { - return false; - } - }, - construct (data) { - return reconstructFunction(data); - }, - predicate (object) { - return object instanceof Function; - }, - represent (object) { - return object.toString(); - } -}); -function isHexCode(c) { - return 0x30 <= c && c <= 0x39 || 0x41 <= c && c <= 0x46 || 0x61 <= c && c <= 0x66; -} -function isOctCode(c) { - return 0x30 <= c && c <= 0x37; -} -function isDecCode(c) { - return 0x30 <= c && c <= 0x39; -} -function resolveYamlInteger(data) { - const max = data.length; - let index = 0; - let hasDigits = false; - if (!max) return false; - let ch = data[index]; - if (ch === "-" || ch === "+") { - ch = data[++index]; - } - if (ch === "0") { - if (index + 1 === max) return true; - ch = data[++index]; - if (ch === "b") { - index++; - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (ch !== "0" && ch !== "1") return false; - hasDigits = true; - } - return hasDigits && ch !== "_"; - } - if (ch === "x") { - index++; - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (!isHexCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== "_"; - } - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (!isOctCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== "_"; - } - if (ch === "_") return false; - for(; index < max; index++){ - ch = data[index]; - if (ch === "_") continue; - if (ch === ":") break; - if (!isDecCode(data.charCodeAt(index))) { - return false; - } - hasDigits = true; - } - if (!hasDigits || ch === "_") return false; - if (ch !== ":") return true; - return /^(:[0-5]?[0-9])+$/.test(data.slice(index)); -} -function constructYamlInteger(data) { - let value = data; - const digits = []; - if (value.indexOf("_") !== -1) { - value = value.replace(/_/g, ""); - } - let sign = 1; - let ch = value[0]; - if (ch === "-" || ch === "+") { - if (ch === "-") sign = -1; - value = value.slice(1); - ch = value[0]; - } - if (value === "0") return 0; - if (ch === "0") { - if (value[1] === "b") return sign * parseInt(value.slice(2), 2); - if (value[1] === "x") return sign * parseInt(value, 16); - return sign * parseInt(value, 8); - } - if (value.indexOf(":") !== -1) { - value.split(":").forEach((v)=>{ - digits.unshift(parseInt(v, 10)); - }); - let valueInt = 0; - let base = 1; - digits.forEach((d)=>{ - valueInt += d * base; - base *= 60; - }); - return sign * valueInt; - } - return sign * parseInt(value, 10); -} -function isInteger(object) { - return Object.prototype.toString.call(object) === "[object Number]" && object % 1 === 0 && !isNegativeZero(object); -} -const __int = new Type("tag:yaml.org,2002:int", { - construct: constructYamlInteger, - defaultStyle: "decimal", - kind: "scalar", - predicate: isInteger, - represent: { - binary (obj) { - return obj >= 0 ? `0b${obj.toString(2)}` : `-0b${obj.toString(2).slice(1)}`; - }, - octal (obj) { - return obj >= 0 ? `0${obj.toString(8)}` : `-0${obj.toString(8).slice(1)}`; - }, - decimal (obj) { - return obj.toString(10); - }, - hexadecimal (obj) { - return obj >= 0 ? `0x${obj.toString(16).toUpperCase()}` : `-0x${obj.toString(16).toUpperCase().slice(1)}`; - } - }, - resolve: resolveYamlInteger, - styleAliases: { - binary: [ - 2, - "bin" - ], - decimal: [ - 10, - "dec" - ], - hexadecimal: [ - 16, - "hex" - ], - octal: [ - 8, - "oct" - ] - } -}); -const map = new Type("tag:yaml.org,2002:map", { - construct (data) { - return data !== null ? data : {}; - }, - kind: "mapping" -}); -function resolveYamlMerge(data) { - return data === "<<" || data === null; -} -const merge = new Type("tag:yaml.org,2002:merge", { - kind: "scalar", - resolve: resolveYamlMerge -}); -function resolveYamlNull(data) { - const max = data.length; - return max === 1 && data === "~" || max === 4 && (data === "null" || data === "Null" || data === "NULL"); -} -function constructYamlNull() { - return null; -} -function isNull(object) { - return object === null; -} -const nil = new Type("tag:yaml.org,2002:null", { - construct: constructYamlNull, - defaultStyle: "lowercase", - kind: "scalar", - predicate: isNull, - represent: { - canonical () { - return "~"; - }, - lowercase () { - return "null"; - }, - uppercase () { - return "NULL"; - }, - camelcase () { - return "Null"; - } - }, - resolve: resolveYamlNull -}); -const { hasOwn } = Object; -const _toString = Object.prototype.toString; -function resolveYamlOmap(data) { - const objectKeys = []; - let pairKey = ""; - let pairHasKey = false; - for (const pair of data){ - pairHasKey = false; - if (_toString.call(pair) !== "[object Object]") return false; - for(pairKey in pair){ - if (hasOwn(pair, pairKey)) { - if (!pairHasKey) pairHasKey = true; - else return false; - } - } - if (!pairHasKey) return false; - if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); - else return false; - } - return true; -} -function constructYamlOmap(data) { - return data !== null ? data : []; -} -const omap = new Type("tag:yaml.org,2002:omap", { - construct: constructYamlOmap, - kind: "sequence", - resolve: resolveYamlOmap -}); -const _toString1 = Object.prototype.toString; -function resolveYamlPairs(data) { - const result = Array.from({ - length: data.length - }); - for(let index = 0; index < data.length; index++){ - const pair = data[index]; - if (_toString1.call(pair) !== "[object Object]") return false; - const keys = Object.keys(pair); - if (keys.length !== 1) return false; - result[index] = [ - keys[0], - pair[keys[0]] - ]; - } - return true; -} -function constructYamlPairs(data) { - if (data === null) return []; - const result = Array.from({ - length: data.length - }); - for(let index = 0; index < data.length; index += 1){ - const pair = data[index]; - const keys = Object.keys(pair); - result[index] = [ - keys[0], - pair[keys[0]] - ]; - } - return result; -} -const pairs = new Type("tag:yaml.org,2002:pairs", { - construct: constructYamlPairs, - kind: "sequence", - resolve: resolveYamlPairs -}); -const REGEXP = /^\/(?[\s\S]+)\/(?[gismuy]*)$/; -const regexp = new Type("tag:yaml.org,2002:js/regexp", { - kind: "scalar", - resolve (data) { - if (data === null || !data.length) { - return false; - } - const regexp = `${data}`; - if (regexp.charAt(0) === "/") { - if (!REGEXP.test(data)) { - return false; - } - const modifiers = [ - ...regexp.match(REGEXP)?.groups?.modifiers ?? "" - ]; - if (new Set(modifiers).size < modifiers.length) { - return false; - } - } - return true; - }, - construct (data) { - const { regexp = `${data}`, modifiers = "" } = `${data}`.match(REGEXP)?.groups ?? {}; - return new RegExp(regexp, modifiers); - }, - predicate (object) { - return object instanceof RegExp; - }, - represent (object) { - return object.toString(); - } -}); -const seq = new Type("tag:yaml.org,2002:seq", { - construct (data) { - return data !== null ? data : []; - }, - kind: "sequence" -}); -const { hasOwn: hasOwn1 } = Object; -function resolveYamlSet(data) { - if (data === null) return true; - for(const key in data){ - if (hasOwn1(data, key)) { - if (data[key] !== null) return false; - } - } - return true; -} -function constructYamlSet(data) { - return data !== null ? data : {}; -} -const set = new Type("tag:yaml.org,2002:set", { - construct: constructYamlSet, - kind: "mapping", - resolve: resolveYamlSet -}); -const str = new Type("tag:yaml.org,2002:str", { - construct (data) { - return data !== null ? data : ""; - }, - kind: "scalar" -}); -const YAML_DATE_REGEXP = new RegExp("^([0-9][0-9][0-9][0-9])" + "-([0-9][0-9])" + "-([0-9][0-9])$"); -const YAML_TIMESTAMP_REGEXP = new RegExp("^([0-9][0-9][0-9][0-9])" + "-([0-9][0-9]?)" + "-([0-9][0-9]?)" + "(?:[Tt]|[ \\t]+)" + "([0-9][0-9]?)" + ":([0-9][0-9])" + ":([0-9][0-9])" + "(?:\\.([0-9]*))?" + "(?:[ \\t]*(Z|([-+])([0-9][0-9]?)" + "(?::([0-9][0-9]))?))?$"); -function resolveYamlTimestamp(data) { - if (data === null) return false; - if (YAML_DATE_REGEXP.exec(data) !== null) return true; - if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; - return false; -} -function constructYamlTimestamp(data) { - let match = YAML_DATE_REGEXP.exec(data); - if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); - if (match === null) throw new Error("Date resolve error"); - const year = +match[1]; - const month = +match[2] - 1; - const day = +match[3]; - if (!match[4]) { - return new Date(Date.UTC(year, month, day)); - } - const hour = +match[4]; - const minute = +match[5]; - const second = +match[6]; - let fraction = 0; - if (match[7]) { - let partFraction = match[7].slice(0, 3); - while(partFraction.length < 3){ - partFraction += "0"; - } - fraction = +partFraction; - } - let delta = null; - if (match[9]) { - const tzHour = +match[10]; - const tzMinute = +(match[11] || 0); - delta = (tzHour * 60 + tzMinute) * 60000; - if (match[9] === "-") delta = -delta; - } - const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); - if (delta) date.setTime(date.getTime() - delta); - return date; -} -function representYamlTimestamp(date) { - return date.toISOString(); -} -const timestamp = new Type("tag:yaml.org,2002:timestamp", { - construct: constructYamlTimestamp, - instanceOf: Date, - kind: "scalar", - represent: representYamlTimestamp, - resolve: resolveYamlTimestamp -}); -const undefinedType = new Type("tag:yaml.org,2002:js/undefined", { - kind: "scalar", - resolve () { - return true; - }, - construct () { - return undefined; - }, - predicate (object) { - return typeof object === "undefined"; - }, - represent () { - return ""; - } -}); -const failsafe = new Schema({ - explicit: [ - str, - seq, - map - ] -}); -const json = new Schema({ - implicit: [ - nil, - bool, - __int, - __float - ], - include: [ - failsafe - ] -}); -const core = new Schema({ - include: [ - json - ] -}); -const def = new Schema({ - explicit: [ - binary, - omap, - pairs, - set - ], - implicit: [ - timestamp, - merge - ], - include: [ - core - ] -}); -const extended = new Schema({ - explicit: [ - regexp, - undefinedType - ], - include: [ - def - ] -}); -class State { - schema; - constructor(schema = def){ - this.schema = schema; - } -} -class LoaderState extends State { - input; - documents; - length; - lineIndent; - lineStart; - position; - line; - filename; - onWarning; - legacy; - json; - listener; - implicitTypes; - typeMap; - version; - checkLineBreaks; - tagMap; - anchorMap; - tag; - anchor; - kind; - result; - constructor(input, { filename, schema, onWarning, legacy = false, json = false, listener = null }){ - super(schema); - this.input = input; - this.documents = []; - this.lineIndent = 0; - this.lineStart = 0; - this.position = 0; - this.line = 0; - this.result = ""; - this.filename = filename; - this.onWarning = onWarning; - this.legacy = legacy; - this.json = json; - this.listener = listener; - this.implicitTypes = this.schema.compiledImplicit; - this.typeMap = this.schema.compiledTypeMap; - this.length = input.length; - } -} -const { hasOwn: hasOwn2 } = Object; -const CONTEXT_BLOCK_IN = 3; -const CONTEXT_BLOCK_OUT = 4; -const CHOMPING_STRIP = 2; -const CHOMPING_KEEP = 3; -const PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; -const PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; -const PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; -const PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; -const PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; -function _class(obj) { - return Object.prototype.toString.call(obj); -} -function isEOL(c) { - return c === 0x0a || c === 0x0d; -} -function isWhiteSpace(c) { - return c === 0x09 || c === 0x20; -} -function isWsOrEol(c) { - return c === 0x09 || c === 0x20 || c === 0x0a || c === 0x0d; -} -function isFlowIndicator(c) { - return c === 0x2c || c === 0x5b || c === 0x5d || c === 0x7b || c === 0x7d; -} -function fromHexCode(c) { - if (0x30 <= c && c <= 0x39) { - return c - 0x30; - } - const lc = c | 0x20; - if (0x61 <= lc && lc <= 0x66) { - return lc - 0x61 + 10; - } - return -1; -} -function escapedHexLen(c) { - if (c === 0x78) { - return 2; - } - if (c === 0x75) { - return 4; - } - if (c === 0x55) { - return 8; - } - return 0; -} -function fromDecimalCode(c) { - if (0x30 <= c && c <= 0x39) { - return c - 0x30; - } - return -1; -} -function simpleEscapeSequence(c) { - return c === 0x30 ? "\x00" : c === 0x61 ? "\x07" : c === 0x62 ? "\x08" : c === 0x74 ? "\x09" : c === 0x09 ? "\x09" : c === 0x6e ? "\x0A" : c === 0x76 ? "\x0B" : c === 0x66 ? "\x0C" : c === 0x72 ? "\x0D" : c === 0x65 ? "\x1B" : c === 0x20 ? " " : c === 0x22 ? "\x22" : c === 0x2f ? "/" : c === 0x5c ? "\x5C" : c === 0x4e ? "\x85" : c === 0x5f ? "\xA0" : c === 0x4c ? "\u2028" : c === 0x50 ? "\u2029" : ""; -} -function charFromCodepoint(c) { - if (c <= 0xffff) { - return String.fromCharCode(c); - } - return String.fromCharCode((c - 0x010000 >> 10) + 0xd800, (c - 0x010000 & 0x03ff) + 0xdc00); -} -const simpleEscapeCheck = Array.from({ - length: 256 -}); -const simpleEscapeMap = Array.from({ - length: 256 -}); -for(let i = 0; i < 256; i++){ - simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; - simpleEscapeMap[i] = simpleEscapeSequence(i); -} -function generateError(state, message) { - return new YAMLError(message, new Mark(state.filename, state.input, state.position, state.line, state.position - state.lineStart)); -} -function throwError(state, message) { - throw generateError(state, message); -} -function throwWarning(state, message) { - if (state.onWarning) { - state.onWarning.call(null, generateError(state, message)); - } -} -const directiveHandlers = { - YAML (state, _name, ...args) { - if (state.version !== null) { - return throwError(state, "duplication of %YAML directive"); - } - if (args.length !== 1) { - return throwError(state, "YAML directive accepts exactly one argument"); - } - const match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); - if (match === null) { - return throwError(state, "ill-formed argument of the YAML directive"); - } - const major = parseInt(match[1], 10); - const minor = parseInt(match[2], 10); - if (major !== 1) { - return throwError(state, "unacceptable YAML version of the document"); - } - state.version = args[0]; - state.checkLineBreaks = minor < 2; - if (minor !== 1 && minor !== 2) { - return throwWarning(state, "unsupported YAML version of the document"); - } - }, - TAG (state, _name, ...args) { - if (args.length !== 2) { - return throwError(state, "TAG directive accepts exactly two arguments"); - } - const handle = args[0]; - const prefix = args[1]; - if (!PATTERN_TAG_HANDLE.test(handle)) { - return throwError(state, "ill-formed tag handle (first argument) of the TAG directive"); - } - if (state.tagMap && hasOwn2(state.tagMap, handle)) { - return throwError(state, `there is a previously declared suffix for "${handle}" tag handle`); - } - if (!PATTERN_TAG_URI.test(prefix)) { - return throwError(state, "ill-formed tag prefix (second argument) of the TAG directive"); - } - if (typeof state.tagMap === "undefined") { - state.tagMap = {}; - } - state.tagMap[handle] = prefix; - } -}; -function captureSegment(state, start, end, checkJson) { - let result; - if (start < end) { - result = state.input.slice(start, end); - if (checkJson) { - for(let position = 0, length = result.length; position < length; position++){ - const character = result.charCodeAt(position); - if (!(character === 0x09 || 0x20 <= character && character <= 0x10ffff)) { - return throwError(state, "expected valid JSON character"); - } - } - } else if (PATTERN_NON_PRINTABLE.test(result)) { - return throwError(state, "the stream contains non-printable characters"); - } - state.result += result; - } -} -function mergeMappings(state, destination, source, overridableKeys) { - if (!isObject1(source)) { - return throwError(state, "cannot merge mappings; the provided source object is unacceptable"); - } - const keys = Object.keys(source); - for(let i = 0, len = keys.length; i < len; i++){ - const key = keys[i]; - if (!hasOwn2(destination, key)) { - destination[key] = source[key]; - overridableKeys[key] = true; - } - } -} -function storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode, startLine, startPos) { - if (Array.isArray(keyNode)) { - keyNode = Array.prototype.slice.call(keyNode); - for(let index = 0, quantity = keyNode.length; index < quantity; index++){ - if (Array.isArray(keyNode[index])) { - return throwError(state, "nested arrays are not supported inside keys"); - } - if (typeof keyNode === "object" && _class(keyNode[index]) === "[object Object]") { - keyNode[index] = "[object Object]"; - } - } - } - if (typeof keyNode === "object" && _class(keyNode) === "[object Object]") { - keyNode = "[object Object]"; - } - keyNode = String(keyNode); - if (result === null) { - result = {}; - } - if (keyTag === "tag:yaml.org,2002:merge") { - if (Array.isArray(valueNode)) { - for(let index = 0, quantity = valueNode.length; index < quantity; index++){ - mergeMappings(state, result, valueNode[index], overridableKeys); - } - } else { - mergeMappings(state, result, valueNode, overridableKeys); - } - } else { - if (!state.json && !hasOwn2(overridableKeys, keyNode) && hasOwn2(result, keyNode)) { - state.line = startLine || state.line; - state.position = startPos || state.position; - return throwError(state, "duplicated mapping key"); - } - result[keyNode] = valueNode; - delete overridableKeys[keyNode]; - } - return result; -} -function readLineBreak(state) { - const ch = state.input.charCodeAt(state.position); - if (ch === 0x0a) { - state.position++; - } else if (ch === 0x0d) { - state.position++; - if (state.input.charCodeAt(state.position) === 0x0a) { - state.position++; - } - } else { - return throwError(state, "a line break is expected"); - } - state.line += 1; - state.lineStart = state.position; -} -function skipSeparationSpace(state, allowComments, checkIndent) { - let lineBreaks = 0, ch = state.input.charCodeAt(state.position); - while(ch !== 0){ - while(isWhiteSpace(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (allowComments && ch === 0x23) { - do { - ch = state.input.charCodeAt(++state.position); - }while (ch !== 0x0a && ch !== 0x0d && ch !== 0) - } - if (isEOL(ch)) { - readLineBreak(state); - ch = state.input.charCodeAt(state.position); - lineBreaks++; - state.lineIndent = 0; - while(ch === 0x20){ - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); - } - } else { - break; - } - } - if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { - throwWarning(state, "deficient indentation"); - } - return lineBreaks; -} -function testDocumentSeparator(state) { - let _position = state.position; - let ch = state.input.charCodeAt(_position); - if ((ch === 0x2d || ch === 0x2e) && ch === state.input.charCodeAt(_position + 1) && ch === state.input.charCodeAt(_position + 2)) { - _position += 3; - ch = state.input.charCodeAt(_position); - if (ch === 0 || isWsOrEol(ch)) { - return true; - } - } - return false; -} -function writeFoldedLines(state, count) { - if (count === 1) { - state.result += " "; - } else if (count > 1) { - state.result += repeat("\n", count - 1); - } -} -function readPlainScalar(state, nodeIndent, withinFlowCollection) { - const kind = state.kind; - const result = state.result; - let ch = state.input.charCodeAt(state.position); - if (isWsOrEol(ch) || isFlowIndicator(ch) || ch === 0x23 || ch === 0x26 || ch === 0x2a || ch === 0x21 || ch === 0x7c || ch === 0x3e || ch === 0x27 || ch === 0x22 || ch === 0x25 || ch === 0x40 || ch === 0x60) { - return false; - } - let following; - if (ch === 0x3f || ch === 0x2d) { - following = state.input.charCodeAt(state.position + 1); - if (isWsOrEol(following) || withinFlowCollection && isFlowIndicator(following)) { - return false; - } - } - state.kind = "scalar"; - state.result = ""; - let captureEnd, captureStart = captureEnd = state.position; - let hasPendingContent = false; - let line = 0; - while(ch !== 0){ - if (ch === 0x3a) { - following = state.input.charCodeAt(state.position + 1); - if (isWsOrEol(following) || withinFlowCollection && isFlowIndicator(following)) { - break; - } - } else if (ch === 0x23) { - const preceding = state.input.charCodeAt(state.position - 1); - if (isWsOrEol(preceding)) { - break; - } - } else if (state.position === state.lineStart && testDocumentSeparator(state) || withinFlowCollection && isFlowIndicator(ch)) { - break; - } else if (isEOL(ch)) { - line = state.line; - const lineStart = state.lineStart; - const lineIndent = state.lineIndent; - skipSeparationSpace(state, false, -1); - if (state.lineIndent >= nodeIndent) { - hasPendingContent = true; - ch = state.input.charCodeAt(state.position); - continue; - } else { - state.position = captureEnd; - state.line = line; - state.lineStart = lineStart; - state.lineIndent = lineIndent; - break; - } - } - if (hasPendingContent) { - captureSegment(state, captureStart, captureEnd, false); - writeFoldedLines(state, state.line - line); - captureStart = captureEnd = state.position; - hasPendingContent = false; - } - if (!isWhiteSpace(ch)) { - captureEnd = state.position + 1; - } - ch = state.input.charCodeAt(++state.position); - } - captureSegment(state, captureStart, captureEnd, false); - if (state.result) { - return true; - } - state.kind = kind; - state.result = result; - return false; -} -function readSingleQuotedScalar(state, nodeIndent) { - let ch, captureStart, captureEnd; - ch = state.input.charCodeAt(state.position); - if (ch !== 0x27) { - return false; - } - state.kind = "scalar"; - state.result = ""; - state.position++; - captureStart = captureEnd = state.position; - while((ch = state.input.charCodeAt(state.position)) !== 0){ - if (ch === 0x27) { - captureSegment(state, captureStart, state.position, true); - ch = state.input.charCodeAt(++state.position); - if (ch === 0x27) { - captureStart = state.position; - state.position++; - captureEnd = state.position; - } else { - return true; - } - } else if (isEOL(ch)) { - captureSegment(state, captureStart, captureEnd, true); - writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); - captureStart = captureEnd = state.position; - } else if (state.position === state.lineStart && testDocumentSeparator(state)) { - return throwError(state, "unexpected end of the document within a single quoted scalar"); - } else { - state.position++; - captureEnd = state.position; - } - } - return throwError(state, "unexpected end of the stream within a single quoted scalar"); -} -function readDoubleQuotedScalar(state, nodeIndent) { - let ch = state.input.charCodeAt(state.position); - if (ch !== 0x22) { - return false; - } - state.kind = "scalar"; - state.result = ""; - state.position++; - let captureEnd, captureStart = captureEnd = state.position; - let tmp; - while((ch = state.input.charCodeAt(state.position)) !== 0){ - if (ch === 0x22) { - captureSegment(state, captureStart, state.position, true); - state.position++; - return true; - } - if (ch === 0x5c) { - captureSegment(state, captureStart, state.position, true); - ch = state.input.charCodeAt(++state.position); - if (isEOL(ch)) { - skipSeparationSpace(state, false, nodeIndent); - } else if (ch < 256 && simpleEscapeCheck[ch]) { - state.result += simpleEscapeMap[ch]; - state.position++; - } else if ((tmp = escapedHexLen(ch)) > 0) { - let hexLength = tmp; - let hexResult = 0; - for(; hexLength > 0; hexLength--){ - ch = state.input.charCodeAt(++state.position); - if ((tmp = fromHexCode(ch)) >= 0) { - hexResult = (hexResult << 4) + tmp; - } else { - return throwError(state, "expected hexadecimal character"); - } - } - state.result += charFromCodepoint(hexResult); - state.position++; - } else { - return throwError(state, "unknown escape sequence"); - } - captureStart = captureEnd = state.position; - } else if (isEOL(ch)) { - captureSegment(state, captureStart, captureEnd, true); - writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); - captureStart = captureEnd = state.position; - } else if (state.position === state.lineStart && testDocumentSeparator(state)) { - return throwError(state, "unexpected end of the document within a double quoted scalar"); - } else { - state.position++; - captureEnd = state.position; - } - } - return throwError(state, "unexpected end of the stream within a double quoted scalar"); -} -function readFlowCollection(state, nodeIndent) { - let ch = state.input.charCodeAt(state.position); - let terminator; - let isMapping = true; - let result = {}; - if (ch === 0x5b) { - terminator = 0x5d; - isMapping = false; - result = []; - } else if (ch === 0x7b) { - terminator = 0x7d; - } else { - return false; - } - if (state.anchor !== null && typeof state.anchor != "undefined" && typeof state.anchorMap != "undefined") { - state.anchorMap[state.anchor] = result; - } - ch = state.input.charCodeAt(++state.position); - const tag = state.tag, anchor = state.anchor; - let readNext = true; - let valueNode, keyNode, keyTag = keyNode = valueNode = null, isExplicitPair, isPair = isExplicitPair = false; - let following = 0, line = 0; - const overridableKeys = {}; - while(ch !== 0){ - skipSeparationSpace(state, true, nodeIndent); - ch = state.input.charCodeAt(state.position); - if (ch === terminator) { - state.position++; - state.tag = tag; - state.anchor = anchor; - state.kind = isMapping ? "mapping" : "sequence"; - state.result = result; - return true; - } - if (!readNext) { - return throwError(state, "missed comma between flow collection entries"); - } - keyTag = keyNode = valueNode = null; - isPair = isExplicitPair = false; - if (ch === 0x3f) { - following = state.input.charCodeAt(state.position + 1); - if (isWsOrEol(following)) { - isPair = isExplicitPair = true; - state.position++; - skipSeparationSpace(state, true, nodeIndent); - } - } - line = state.line; - composeNode(state, nodeIndent, 1, false, true); - keyTag = state.tag || null; - keyNode = state.result; - skipSeparationSpace(state, true, nodeIndent); - ch = state.input.charCodeAt(state.position); - if ((isExplicitPair || state.line === line) && ch === 0x3a) { - isPair = true; - ch = state.input.charCodeAt(++state.position); - skipSeparationSpace(state, true, nodeIndent); - composeNode(state, nodeIndent, 1, false, true); - valueNode = state.result; - } - if (isMapping) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode); - } else if (isPair) { - result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode)); - } else { - result.push(keyNode); - } - skipSeparationSpace(state, true, nodeIndent); - ch = state.input.charCodeAt(state.position); - if (ch === 0x2c) { - readNext = true; - ch = state.input.charCodeAt(++state.position); - } else { - readNext = false; - } - } - return throwError(state, "unexpected end of the stream within a flow collection"); -} -function readBlockScalar(state, nodeIndent) { - let chomping = 1, didReadContent = false, detectedIndent = false, textIndent = nodeIndent, emptyLines = 0, atMoreIndented = false; - let ch = state.input.charCodeAt(state.position); - let folding = false; - if (ch === 0x7c) { - folding = false; - } else if (ch === 0x3e) { - folding = true; - } else { - return false; - } - state.kind = "scalar"; - state.result = ""; - let tmp = 0; - while(ch !== 0){ - ch = state.input.charCodeAt(++state.position); - if (ch === 0x2b || ch === 0x2d) { - if (1 === chomping) { - chomping = ch === 0x2b ? CHOMPING_KEEP : CHOMPING_STRIP; - } else { - return throwError(state, "repeat of a chomping mode identifier"); - } - } else if ((tmp = fromDecimalCode(ch)) >= 0) { - if (tmp === 0) { - return throwError(state, "bad explicit indentation width of a block scalar; it cannot be less than one"); - } else if (!detectedIndent) { - textIndent = nodeIndent + tmp - 1; - detectedIndent = true; - } else { - return throwError(state, "repeat of an indentation width identifier"); - } - } else { - break; - } - } - if (isWhiteSpace(ch)) { - do { - ch = state.input.charCodeAt(++state.position); - }while (isWhiteSpace(ch)) - if (ch === 0x23) { - do { - ch = state.input.charCodeAt(++state.position); - }while (!isEOL(ch) && ch !== 0) - } - } - while(ch !== 0){ - readLineBreak(state); - state.lineIndent = 0; - ch = state.input.charCodeAt(state.position); - while((!detectedIndent || state.lineIndent < textIndent) && ch === 0x20){ - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); - } - if (!detectedIndent && state.lineIndent > textIndent) { - textIndent = state.lineIndent; - } - if (isEOL(ch)) { - emptyLines++; - continue; - } - if (state.lineIndent < textIndent) { - if (chomping === 3) { - state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); - } else if (chomping === 1) { - if (didReadContent) { - state.result += "\n"; - } - } - break; - } - if (folding) { - if (isWhiteSpace(ch)) { - atMoreIndented = true; - state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); - } else if (atMoreIndented) { - atMoreIndented = false; - state.result += repeat("\n", emptyLines + 1); - } else if (emptyLines === 0) { - if (didReadContent) { - state.result += " "; - } - } else { - state.result += repeat("\n", emptyLines); - } - } else { - state.result += repeat("\n", didReadContent ? 1 + emptyLines : emptyLines); - } - didReadContent = true; - detectedIndent = true; - emptyLines = 0; - const captureStart = state.position; - while(!isEOL(ch) && ch !== 0){ - ch = state.input.charCodeAt(++state.position); - } - captureSegment(state, captureStart, state.position, false); - } - return true; -} -function readBlockSequence(state, nodeIndent) { - let line, following, detected = false, ch; - const tag = state.tag, anchor = state.anchor, result = []; - if (state.anchor !== null && typeof state.anchor !== "undefined" && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = result; - } - ch = state.input.charCodeAt(state.position); - while(ch !== 0){ - if (ch !== 0x2d) { - break; - } - following = state.input.charCodeAt(state.position + 1); - if (!isWsOrEol(following)) { - break; - } - detected = true; - state.position++; - if (skipSeparationSpace(state, true, -1)) { - if (state.lineIndent <= nodeIndent) { - result.push(null); - ch = state.input.charCodeAt(state.position); - continue; - } - } - line = state.line; - composeNode(state, nodeIndent, 3, false, true); - result.push(state.result); - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - if ((state.line === line || state.lineIndent > nodeIndent) && ch !== 0) { - return throwError(state, "bad indentation of a sequence entry"); - } else if (state.lineIndent < nodeIndent) { - break; - } - } - if (detected) { - state.tag = tag; - state.anchor = anchor; - state.kind = "sequence"; - state.result = result; - return true; - } - return false; -} -function readBlockMapping(state, nodeIndent, flowIndent) { - const tag = state.tag, anchor = state.anchor, result = {}, overridableKeys = {}; - let following, allowCompact = false, line, pos, keyTag = null, keyNode = null, valueNode = null, atExplicitKey = false, detected = false, ch; - if (state.anchor !== null && typeof state.anchor !== "undefined" && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = result; - } - ch = state.input.charCodeAt(state.position); - while(ch !== 0){ - following = state.input.charCodeAt(state.position + 1); - line = state.line; - pos = state.position; - if ((ch === 0x3f || ch === 0x3a) && isWsOrEol(following)) { - if (ch === 0x3f) { - if (atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); - keyTag = keyNode = valueNode = null; - } - detected = true; - atExplicitKey = true; - allowCompact = true; - } else if (atExplicitKey) { - atExplicitKey = false; - allowCompact = true; - } else { - return throwError(state, "incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line"); - } - state.position += 1; - ch = following; - } else if (composeNode(state, flowIndent, 2, false, true)) { - if (state.line === line) { - ch = state.input.charCodeAt(state.position); - while(isWhiteSpace(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (ch === 0x3a) { - ch = state.input.charCodeAt(++state.position); - if (!isWsOrEol(ch)) { - return throwError(state, "a whitespace character is expected after the key-value separator within a block mapping"); - } - if (atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); - keyTag = keyNode = valueNode = null; - } - detected = true; - atExplicitKey = false; - allowCompact = false; - keyTag = state.tag; - keyNode = state.result; - } else if (detected) { - return throwError(state, "can not read an implicit mapping pair; a colon is missed"); - } else { - state.tag = tag; - state.anchor = anchor; - return true; - } - } else if (detected) { - return throwError(state, "can not read a block mapping entry; a multiline key may not be an implicit key"); - } else { - state.tag = tag; - state.anchor = anchor; - return true; - } - } else { - break; - } - if (state.line === line || state.lineIndent > nodeIndent) { - if (composeNode(state, nodeIndent, 4, true, allowCompact)) { - if (atExplicitKey) { - keyNode = state.result; - } else { - valueNode = state.result; - } - } - if (!atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, valueNode, line, pos); - keyTag = keyNode = valueNode = null; - } - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - } - if (state.lineIndent > nodeIndent && ch !== 0) { - return throwError(state, "bad indentation of a mapping entry"); - } else if (state.lineIndent < nodeIndent) { - break; - } - } - if (atExplicitKey) { - storeMappingPair(state, result, overridableKeys, keyTag, keyNode, null); - } - if (detected) { - state.tag = tag; - state.anchor = anchor; - state.kind = "mapping"; - state.result = result; - } - return detected; -} -function readTagProperty(state) { - let position, isVerbatim = false, isNamed = false, tagHandle = "", tagName, ch; - ch = state.input.charCodeAt(state.position); - if (ch !== 0x21) return false; - if (state.tag !== null) { - return throwError(state, "duplication of a tag property"); - } - ch = state.input.charCodeAt(++state.position); - if (ch === 0x3c) { - isVerbatim = true; - ch = state.input.charCodeAt(++state.position); - } else if (ch === 0x21) { - isNamed = true; - tagHandle = "!!"; - ch = state.input.charCodeAt(++state.position); - } else { - tagHandle = "!"; - } - position = state.position; - if (isVerbatim) { - do { - ch = state.input.charCodeAt(++state.position); - }while (ch !== 0 && ch !== 0x3e) - if (state.position < state.length) { - tagName = state.input.slice(position, state.position); - ch = state.input.charCodeAt(++state.position); - } else { - return throwError(state, "unexpected end of the stream within a verbatim tag"); - } - } else { - while(ch !== 0 && !isWsOrEol(ch)){ - if (ch === 0x21) { - if (!isNamed) { - tagHandle = state.input.slice(position - 1, state.position + 1); - if (!PATTERN_TAG_HANDLE.test(tagHandle)) { - return throwError(state, "named tag handle cannot contain such characters"); - } - isNamed = true; - position = state.position + 1; - } else { - return throwError(state, "tag suffix cannot contain exclamation marks"); - } - } - ch = state.input.charCodeAt(++state.position); - } - tagName = state.input.slice(position, state.position); - if (PATTERN_FLOW_INDICATORS.test(tagName)) { - return throwError(state, "tag suffix cannot contain flow indicator characters"); - } - } - if (tagName && !PATTERN_TAG_URI.test(tagName)) { - return throwError(state, `tag name cannot contain such characters: ${tagName}`); - } - if (isVerbatim) { - state.tag = tagName; - } else if (typeof state.tagMap !== "undefined" && hasOwn2(state.tagMap, tagHandle)) { - state.tag = state.tagMap[tagHandle] + tagName; - } else if (tagHandle === "!") { - state.tag = `!${tagName}`; - } else if (tagHandle === "!!") { - state.tag = `tag:yaml.org,2002:${tagName}`; - } else { - return throwError(state, `undeclared tag handle "${tagHandle}"`); - } - return true; -} -function readAnchorProperty(state) { - let ch = state.input.charCodeAt(state.position); - if (ch !== 0x26) return false; - if (state.anchor !== null) { - return throwError(state, "duplication of an anchor property"); - } - ch = state.input.charCodeAt(++state.position); - const position = state.position; - while(ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (state.position === position) { - return throwError(state, "name of an anchor node must contain at least one character"); - } - state.anchor = state.input.slice(position, state.position); - return true; -} -function readAlias(state) { - let ch = state.input.charCodeAt(state.position); - if (ch !== 0x2a) return false; - ch = state.input.charCodeAt(++state.position); - const _position = state.position; - while(ch !== 0 && !isWsOrEol(ch) && !isFlowIndicator(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (state.position === _position) { - return throwError(state, "name of an alias node must contain at least one character"); - } - const alias = state.input.slice(_position, state.position); - if (typeof state.anchorMap !== "undefined" && !hasOwn2(state.anchorMap, alias)) { - return throwError(state, `unidentified alias "${alias}"`); - } - if (typeof state.anchorMap !== "undefined") { - state.result = state.anchorMap[alias]; - } - skipSeparationSpace(state, true, -1); - return true; -} -function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { - let allowBlockScalars, allowBlockCollections, indentStatus = 1, atNewLine = false, hasContent = false, type, flowIndent, blockIndent; - if (state.listener && state.listener !== null) { - state.listener("open", state); - } - state.tag = null; - state.anchor = null; - state.kind = null; - state.result = null; - const allowBlockStyles = allowBlockScalars = allowBlockCollections = CONTEXT_BLOCK_OUT === nodeContext || CONTEXT_BLOCK_IN === nodeContext; - if (allowToSeek) { - if (skipSeparationSpace(state, true, -1)) { - atNewLine = true; - if (state.lineIndent > parentIndent) { - indentStatus = 1; - } else if (state.lineIndent === parentIndent) { - indentStatus = 0; - } else if (state.lineIndent < parentIndent) { - indentStatus = -1; - } - } - } - if (indentStatus === 1) { - while(readTagProperty(state) || readAnchorProperty(state)){ - if (skipSeparationSpace(state, true, -1)) { - atNewLine = true; - allowBlockCollections = allowBlockStyles; - if (state.lineIndent > parentIndent) { - indentStatus = 1; - } else if (state.lineIndent === parentIndent) { - indentStatus = 0; - } else if (state.lineIndent < parentIndent) { - indentStatus = -1; - } - } else { - allowBlockCollections = false; - } - } - } - if (allowBlockCollections) { - allowBlockCollections = atNewLine || allowCompact; - } - if (indentStatus === 1 || 4 === nodeContext) { - const cond = 1 === nodeContext || 2 === nodeContext; - flowIndent = cond ? parentIndent : parentIndent + 1; - blockIndent = state.position - state.lineStart; - if (indentStatus === 1) { - if (allowBlockCollections && (readBlockSequence(state, blockIndent) || readBlockMapping(state, blockIndent, flowIndent)) || readFlowCollection(state, flowIndent)) { - hasContent = true; - } else { - if (allowBlockScalars && readBlockScalar(state, flowIndent) || readSingleQuotedScalar(state, flowIndent) || readDoubleQuotedScalar(state, flowIndent)) { - hasContent = true; - } else if (readAlias(state)) { - hasContent = true; - if (state.tag !== null || state.anchor !== null) { - return throwError(state, "alias node should not have Any properties"); - } - } else if (readPlainScalar(state, flowIndent, 1 === nodeContext)) { - hasContent = true; - if (state.tag === null) { - state.tag = "?"; - } - } - if (state.anchor !== null && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = state.result; - } - } - } else if (indentStatus === 0) { - hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); - } - } - if (state.tag !== null && state.tag !== "!") { - if (state.tag === "?") { - for(let typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex++){ - type = state.implicitTypes[typeIndex]; - if (type.resolve(state.result)) { - state.result = type.construct(state.result); - state.tag = type.tag; - if (state.anchor !== null && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = state.result; - } - break; - } - } - } else if (hasOwn2(state.typeMap[state.kind || "fallback"], state.tag)) { - type = state.typeMap[state.kind || "fallback"][state.tag]; - if (state.result !== null && type.kind !== state.kind) { - return throwError(state, `unacceptable node kind for !<${state.tag}> tag; it should be "${type.kind}", not "${state.kind}"`); - } - if (!type.resolve(state.result)) { - return throwError(state, `cannot resolve a node with !<${state.tag}> explicit tag`); - } else { - state.result = type.construct(state.result); - if (state.anchor !== null && typeof state.anchorMap !== "undefined") { - state.anchorMap[state.anchor] = state.result; - } - } - } else { - return throwError(state, `unknown tag !<${state.tag}>`); - } - } - if (state.listener && state.listener !== null) { - state.listener("close", state); - } - return state.tag !== null || state.anchor !== null || hasContent; -} -function readDocument(state) { - const documentStart = state.position; - let position, directiveName, directiveArgs, hasDirectives = false, ch; - state.version = null; - state.checkLineBreaks = state.legacy; - state.tagMap = {}; - state.anchorMap = {}; - while((ch = state.input.charCodeAt(state.position)) !== 0){ - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - if (state.lineIndent > 0 || ch !== 0x25) { - break; - } - hasDirectives = true; - ch = state.input.charCodeAt(++state.position); - position = state.position; - while(ch !== 0 && !isWsOrEol(ch)){ - ch = state.input.charCodeAt(++state.position); - } - directiveName = state.input.slice(position, state.position); - directiveArgs = []; - if (directiveName.length < 1) { - return throwError(state, "directive name must not be less than one character in length"); - } - while(ch !== 0){ - while(isWhiteSpace(ch)){ - ch = state.input.charCodeAt(++state.position); - } - if (ch === 0x23) { - do { - ch = state.input.charCodeAt(++state.position); - }while (ch !== 0 && !isEOL(ch)) - break; - } - if (isEOL(ch)) break; - position = state.position; - while(ch !== 0 && !isWsOrEol(ch)){ - ch = state.input.charCodeAt(++state.position); - } - directiveArgs.push(state.input.slice(position, state.position)); - } - if (ch !== 0) readLineBreak(state); - if (hasOwn2(directiveHandlers, directiveName)) { - directiveHandlers[directiveName](state, directiveName, ...directiveArgs); - } else { - throwWarning(state, `unknown document directive "${directiveName}"`); - } - } - skipSeparationSpace(state, true, -1); - if (state.lineIndent === 0 && state.input.charCodeAt(state.position) === 0x2d && state.input.charCodeAt(state.position + 1) === 0x2d && state.input.charCodeAt(state.position + 2) === 0x2d) { - state.position += 3; - skipSeparationSpace(state, true, -1); - } else if (hasDirectives) { - return throwError(state, "directives end mark is expected"); - } - composeNode(state, state.lineIndent - 1, 4, false, true); - skipSeparationSpace(state, true, -1); - if (state.checkLineBreaks && PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { - throwWarning(state, "non-ASCII line breaks are interpreted as content"); - } - state.documents.push(state.result); - if (state.position === state.lineStart && testDocumentSeparator(state)) { - if (state.input.charCodeAt(state.position) === 0x2e) { - state.position += 3; - skipSeparationSpace(state, true, -1); - } - return; - } - if (state.position < state.length - 1) { - return throwError(state, "end of the stream or a document separator is expected"); - } -} -function loadDocuments(input, options) { - input = String(input); - options = options || {}; - if (input.length !== 0) { - if (input.charCodeAt(input.length - 1) !== 0x0a && input.charCodeAt(input.length - 1) !== 0x0d) { - input += "\n"; - } - if (input.charCodeAt(0) === 0xfeff) { - input = input.slice(1); - } - } - const state = new LoaderState(input, options); - state.input += "\0"; - while(state.input.charCodeAt(state.position) === 0x20){ - state.lineIndent += 1; - state.position += 1; - } - while(state.position < state.length - 1){ - readDocument(state); - } - return state.documents; -} -function isCbFunction(fn) { - return typeof fn === "function"; -} -function loadAll(input, iteratorOrOption, options) { - if (!isCbFunction(iteratorOrOption)) { - return loadDocuments(input, iteratorOrOption); - } - const documents = loadDocuments(input, options); - const iterator = iteratorOrOption; - for(let index = 0, length = documents.length; index < length; index++){ - iterator(documents[index]); - } - return void 0; -} -function load(input, options) { - const documents = loadDocuments(input, options); - if (documents.length === 0) { - return; - } - if (documents.length === 1) { - return documents[0]; - } - throw new YAMLError("expected a single document in the stream, but found more"); -} -function parse(content, options) { - return load(content, options); -} -function parseAll(content, iterator, options) { - return loadAll(content, iterator, options); -} -const { hasOwn: hasOwn3 } = Object; -function compileStyleMap(schema, map) { - if (typeof map === "undefined" || map === null) return {}; - let type; - const result = {}; - const keys = Object.keys(map); - let tag, style; - for(let index = 0, length = keys.length; index < length; index += 1){ - tag = keys[index]; - style = String(map[tag]); - if (tag.slice(0, 2) === "!!") { - tag = `tag:yaml.org,2002:${tag.slice(2)}`; - } - type = schema.compiledTypeMap.fallback[tag]; - if (type && typeof type.styleAliases !== "undefined" && hasOwn3(type.styleAliases, style)) { - style = type.styleAliases[style]; - } - result[tag] = style; - } - return result; -} -class DumperState extends State { - indent; - noArrayIndent; - skipInvalid; - flowLevel; - sortKeys; - lineWidth; - noRefs; - noCompatMode; - condenseFlow; - implicitTypes; - explicitTypes; - tag = null; - result = ""; - duplicates = []; - usedDuplicates = []; - styleMap; - dump; - constructor({ schema, indent = 2, noArrayIndent = false, skipInvalid = false, flowLevel = -1, styles = null, sortKeys = false, lineWidth = 80, noRefs = false, noCompatMode = false, condenseFlow = false }){ - super(schema); - this.indent = Math.max(1, indent); - this.noArrayIndent = noArrayIndent; - this.skipInvalid = skipInvalid; - this.flowLevel = flowLevel; - this.styleMap = compileStyleMap(this.schema, styles); - this.sortKeys = sortKeys; - this.lineWidth = lineWidth; - this.noRefs = noRefs; - this.noCompatMode = noCompatMode; - this.condenseFlow = condenseFlow; - this.implicitTypes = this.schema.compiledImplicit; - this.explicitTypes = this.schema.compiledExplicit; - } -} -const _toString2 = Object.prototype.toString; -const { hasOwn: hasOwn4 } = Object; -const ESCAPE_SEQUENCES = {}; -ESCAPE_SEQUENCES[0x00] = "\\0"; -ESCAPE_SEQUENCES[0x07] = "\\a"; -ESCAPE_SEQUENCES[0x08] = "\\b"; -ESCAPE_SEQUENCES[0x09] = "\\t"; -ESCAPE_SEQUENCES[0x0a] = "\\n"; -ESCAPE_SEQUENCES[0x0b] = "\\v"; -ESCAPE_SEQUENCES[0x0c] = "\\f"; -ESCAPE_SEQUENCES[0x0d] = "\\r"; -ESCAPE_SEQUENCES[0x1b] = "\\e"; -ESCAPE_SEQUENCES[0x22] = '\\"'; -ESCAPE_SEQUENCES[0x5c] = "\\\\"; -ESCAPE_SEQUENCES[0x85] = "\\N"; -ESCAPE_SEQUENCES[0xa0] = "\\_"; -ESCAPE_SEQUENCES[0x2028] = "\\L"; -ESCAPE_SEQUENCES[0x2029] = "\\P"; -const DEPRECATED_BOOLEANS_SYNTAX = [ - "y", - "Y", - "yes", - "Yes", - "YES", - "on", - "On", - "ON", - "n", - "N", - "no", - "No", - "NO", - "off", - "Off", - "OFF" -]; -function encodeHex(character) { - const string = character.toString(16).toUpperCase(); - let handle; - let length; - if (character <= 0xff) { - handle = "x"; - length = 2; - } else if (character <= 0xffff) { - handle = "u"; - length = 4; - } else if (character <= 0xffffffff) { - handle = "U"; - length = 8; - } else { - throw new YAMLError("code point within a string may not be greater than 0xFFFFFFFF"); - } - return `\\${handle}${repeat("0", length - string.length)}${string}`; -} -function indentString(string, spaces) { - const ind = repeat(" ", spaces), length = string.length; - let position = 0, next = -1, result = "", line; - while(position < length){ - next = string.indexOf("\n", position); - if (next === -1) { - line = string.slice(position); - position = length; - } else { - line = string.slice(position, next + 1); - position = next + 1; - } - if (line.length && line !== "\n") result += ind; - result += line; - } - return result; -} -function generateNextLine(state, level) { - return `\n${repeat(" ", state.indent * level)}`; -} -function testImplicitResolving(state, str) { - let type; - for(let index = 0, length = state.implicitTypes.length; index < length; index += 1){ - type = state.implicitTypes[index]; - if (type.resolve(str)) { - return true; - } - } - return false; -} -function isWhitespace(c) { - return c === 0x20 || c === 0x09; -} -function isPrintable(c) { - return 0x00020 <= c && c <= 0x00007e || 0x000a1 <= c && c <= 0x00d7ff && c !== 0x2028 && c !== 0x2029 || 0x0e000 <= c && c <= 0x00fffd && c !== 0xfeff || 0x10000 <= c && c <= 0x10ffff; -} -function isPlainSafe(c) { - return isPrintable(c) && c !== 0xfeff && c !== 0x2c && c !== 0x5b && c !== 0x5d && c !== 0x7b && c !== 0x7d && c !== 0x3a && c !== 0x23; -} -function isPlainSafeFirst(c) { - return isPrintable(c) && c !== 0xfeff && !isWhitespace(c) && c !== 0x2d && c !== 0x3f && c !== 0x3a && c !== 0x2c && c !== 0x5b && c !== 0x5d && c !== 0x7b && c !== 0x7d && c !== 0x23 && c !== 0x26 && c !== 0x2a && c !== 0x21 && c !== 0x7c && c !== 0x3e && c !== 0x27 && c !== 0x22 && c !== 0x25 && c !== 0x40 && c !== 0x60; -} -function needIndentIndicator(string) { - const leadingSpaceRe = /^\n* /; - return leadingSpaceRe.test(string); -} -const STYLE_PLAIN = 1, STYLE_SINGLE = 2, STYLE_LITERAL = 3, STYLE_FOLDED = 4, STYLE_DOUBLE = 5; -function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) { - const shouldTrackWidth = lineWidth !== -1; - let hasLineBreak = false, hasFoldableLine = false, previousLineBreak = -1, plain = isPlainSafeFirst(string.charCodeAt(0)) && !isWhitespace(string.charCodeAt(string.length - 1)); - let __char, i; - if (singleLineOnly) { - for(i = 0; i < string.length; i++){ - __char = string.charCodeAt(i); - if (!isPrintable(__char)) { - return 5; - } - plain = plain && isPlainSafe(__char); - } - } else { - for(i = 0; i < string.length; i++){ - __char = string.charCodeAt(i); - if (__char === 0x0a) { - hasLineBreak = true; - if (shouldTrackWidth) { - hasFoldableLine = hasFoldableLine || i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; - previousLineBreak = i; - } - } else if (!isPrintable(__char)) { - return 5; - } - plain = plain && isPlainSafe(__char); - } - hasFoldableLine = hasFoldableLine || shouldTrackWidth && i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; - } - if (!hasLineBreak && !hasFoldableLine) { - return plain && !testAmbiguousType(string) ? 1 : 2; - } - if (indentPerLevel > 9 && needIndentIndicator(string)) { - return 5; - } - return hasFoldableLine ? 4 : 3; -} -function foldLine(line, width) { - if (line === "" || line[0] === " ") return line; - const breakRe = / [^ ]/g; - let match; - let start = 0, end, curr = 0, next = 0; - let result = ""; - while(match = breakRe.exec(line)){ - next = match.index; - if (next - start > width) { - end = curr > start ? curr : next; - result += `\n${line.slice(start, end)}`; - start = end + 1; - } - curr = next; - } - result += "\n"; - if (line.length - start > width && curr > start) { - result += `${line.slice(start, curr)}\n${line.slice(curr + 1)}`; - } else { - result += line.slice(start); - } - return result.slice(1); -} -function dropEndingNewline(string) { - return string[string.length - 1] === "\n" ? string.slice(0, -1) : string; -} -function foldString(string, width) { - const lineRe = /(\n+)([^\n]*)/g; - let result = (()=>{ - let nextLF = string.indexOf("\n"); - nextLF = nextLF !== -1 ? nextLF : string.length; - lineRe.lastIndex = nextLF; - return foldLine(string.slice(0, nextLF), width); - })(); - let prevMoreIndented = string[0] === "\n" || string[0] === " "; - let moreIndented; - let match; - while(match = lineRe.exec(string)){ - const prefix = match[1], line = match[2]; - moreIndented = line[0] === " "; - result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width); - prevMoreIndented = moreIndented; - } - return result; -} -function escapeString(string) { - let result = ""; - let __char, nextChar; - let escapeSeq; - for(let i = 0; i < string.length; i++){ - __char = string.charCodeAt(i); - if (__char >= 0xd800 && __char <= 0xdbff) { - nextChar = string.charCodeAt(i + 1); - if (nextChar >= 0xdc00 && nextChar <= 0xdfff) { - result += encodeHex((__char - 0xd800) * 0x400 + nextChar - 0xdc00 + 0x10000); - i++; - continue; - } - } - escapeSeq = ESCAPE_SEQUENCES[__char]; - result += !escapeSeq && isPrintable(__char) ? string[i] : escapeSeq || encodeHex(__char); - } - return result; -} -function blockHeader(string, indentPerLevel) { - const indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ""; - const clip = string[string.length - 1] === "\n"; - const keep = clip && (string[string.length - 2] === "\n" || string === "\n"); - const chomp = keep ? "+" : clip ? "" : "-"; - return `${indentIndicator}${chomp}\n`; -} -function writeScalar(state, string, level, iskey) { - state.dump = (()=>{ - if (string.length === 0) { - return "''"; - } - if (!state.noCompatMode && DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) { - return `'${string}'`; - } - const indent = state.indent * Math.max(1, level); - const lineWidth = state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); - const singleLineOnly = iskey || state.flowLevel > -1 && level >= state.flowLevel; - function testAmbiguity(str) { - return testImplicitResolving(state, str); - } - switch(chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)){ - case STYLE_PLAIN: - return string; - case STYLE_SINGLE: - return `'${string.replace(/'/g, "''")}'`; - case STYLE_LITERAL: - return `|${blockHeader(string, state.indent)}${dropEndingNewline(indentString(string, indent))}`; - case STYLE_FOLDED: - return `>${blockHeader(string, state.indent)}${dropEndingNewline(indentString(foldString(string, lineWidth), indent))}`; - case STYLE_DOUBLE: - return `"${escapeString(string)}"`; - default: - throw new YAMLError("impossible error: invalid scalar style"); - } - })(); -} -function writeFlowSequence(state, level, object) { - let _result = ""; - const _tag = state.tag; - for(let index = 0, length = object.length; index < length; index += 1){ - if (writeNode(state, level, object[index], false, false)) { - if (index !== 0) _result += `,${!state.condenseFlow ? " " : ""}`; - _result += state.dump; - } - } - state.tag = _tag; - state.dump = `[${_result}]`; -} -function writeBlockSequence(state, level, object, compact = false) { - let _result = ""; - const _tag = state.tag; - for(let index = 0, length = object.length; index < length; index += 1){ - if (writeNode(state, level + 1, object[index], true, true)) { - if (!compact || index !== 0) { - _result += generateNextLine(state, level); - } - if (state.dump && 0x0a === state.dump.charCodeAt(0)) { - _result += "-"; - } else { - _result += "- "; - } - _result += state.dump; - } - } - state.tag = _tag; - state.dump = _result || "[]"; -} -function writeFlowMapping(state, level, object) { - let _result = ""; - const _tag = state.tag, objectKeyList = Object.keys(object); - let pairBuffer, objectKey, objectValue; - for(let index = 0, length = objectKeyList.length; index < length; index += 1){ - pairBuffer = state.condenseFlow ? '"' : ""; - if (index !== 0) pairBuffer += ", "; - objectKey = objectKeyList[index]; - objectValue = object[objectKey]; - if (!writeNode(state, level, objectKey, false, false)) { - continue; - } - if (state.dump.length > 1024) pairBuffer += "? "; - pairBuffer += `${state.dump}${state.condenseFlow ? '"' : ""}:${state.condenseFlow ? "" : " "}`; - if (!writeNode(state, level, objectValue, false, false)) { - continue; - } - pairBuffer += state.dump; - _result += pairBuffer; - } - state.tag = _tag; - state.dump = `{${_result}}`; -} -function writeBlockMapping(state, level, object, compact = false) { - const _tag = state.tag, objectKeyList = Object.keys(object); - let _result = ""; - if (state.sortKeys === true) { - objectKeyList.sort(); - } else if (typeof state.sortKeys === "function") { - objectKeyList.sort(state.sortKeys); - } else if (state.sortKeys) { - throw new YAMLError("sortKeys must be a boolean or a function"); - } - let pairBuffer = "", objectKey, objectValue, explicitPair; - for(let index = 0, length = objectKeyList.length; index < length; index += 1){ - pairBuffer = ""; - if (!compact || index !== 0) { - pairBuffer += generateNextLine(state, level); - } - objectKey = objectKeyList[index]; - objectValue = object[objectKey]; - if (!writeNode(state, level + 1, objectKey, true, true, true)) { - continue; - } - explicitPair = state.tag !== null && state.tag !== "?" || state.dump && state.dump.length > 1024; - if (explicitPair) { - if (state.dump && 0x0a === state.dump.charCodeAt(0)) { - pairBuffer += "?"; - } else { - pairBuffer += "? "; - } - } - pairBuffer += state.dump; - if (explicitPair) { - pairBuffer += generateNextLine(state, level); - } - if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { - continue; - } - if (state.dump && 0x0a === state.dump.charCodeAt(0)) { - pairBuffer += ":"; - } else { - pairBuffer += ": "; - } - pairBuffer += state.dump; - _result += pairBuffer; - } - state.tag = _tag; - state.dump = _result || "{}"; -} -function detectType(state, object, explicit = false) { - const typeList = explicit ? state.explicitTypes : state.implicitTypes; - let type; - let style; - let _result; - for(let index = 0, length = typeList.length; index < length; index += 1){ - type = typeList[index]; - if ((type.instanceOf || type.predicate) && (!type.instanceOf || typeof object === "object" && object instanceof type.instanceOf) && (!type.predicate || type.predicate(object))) { - state.tag = explicit ? type.tag : "?"; - if (type.represent) { - style = state.styleMap[type.tag] || type.defaultStyle; - if (_toString2.call(type.represent) === "[object Function]") { - _result = type.represent(object, style); - } else if (hasOwn4(type.represent, style)) { - _result = type.represent[style](object, style); - } else { - throw new YAMLError(`!<${type.tag}> tag resolver accepts not "${style}" style`); - } - state.dump = _result; - } - return true; - } - } - return false; -} -function writeNode(state, level, object, block, compact, iskey = false) { - state.tag = null; - state.dump = object; - if (!detectType(state, object, false)) { - detectType(state, object, true); - } - const type = _toString2.call(state.dump); - if (block) { - block = state.flowLevel < 0 || state.flowLevel > level; - } - const objectOrArray = type === "[object Object]" || type === "[object Array]"; - let duplicateIndex = -1; - let duplicate = false; - if (objectOrArray) { - duplicateIndex = state.duplicates.indexOf(object); - duplicate = duplicateIndex !== -1; - } - if (state.tag !== null && state.tag !== "?" || duplicate || state.indent !== 2 && level > 0) { - compact = false; - } - if (duplicate && state.usedDuplicates[duplicateIndex]) { - state.dump = `*ref_${duplicateIndex}`; - } else { - if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { - state.usedDuplicates[duplicateIndex] = true; - } - if (type === "[object Object]") { - if (block && Object.keys(state.dump).length !== 0) { - writeBlockMapping(state, level, state.dump, compact); - if (duplicate) { - state.dump = `&ref_${duplicateIndex}${state.dump}`; - } - } else { - writeFlowMapping(state, level, state.dump); - if (duplicate) { - state.dump = `&ref_${duplicateIndex} ${state.dump}`; - } - } - } else if (type === "[object Array]") { - const arrayLevel = state.noArrayIndent && level > 0 ? level - 1 : level; - if (block && state.dump.length !== 0) { - writeBlockSequence(state, arrayLevel, state.dump, compact); - if (duplicate) { - state.dump = `&ref_${duplicateIndex}${state.dump}`; - } - } else { - writeFlowSequence(state, arrayLevel, state.dump); - if (duplicate) { - state.dump = `&ref_${duplicateIndex} ${state.dump}`; - } - } - } else if (type === "[object String]") { - if (state.tag !== "?") { - writeScalar(state, state.dump, level, iskey); - } - } else { - if (state.skipInvalid) return false; - throw new YAMLError(`unacceptable kind of an object to dump ${type}`); - } - if (state.tag !== null && state.tag !== "?") { - state.dump = `!<${state.tag}> ${state.dump}`; - } - } - return true; -} -function inspectNode(object, objects, duplicatesIndexes) { - if (object !== null && typeof object === "object") { - const index = objects.indexOf(object); - if (index !== -1) { - if (duplicatesIndexes.indexOf(index) === -1) { - duplicatesIndexes.push(index); - } - } else { - objects.push(object); - if (Array.isArray(object)) { - for(let idx = 0, length = object.length; idx < length; idx += 1){ - inspectNode(object[idx], objects, duplicatesIndexes); - } - } else { - const objectKeyList = Object.keys(object); - for(let idx = 0, length = objectKeyList.length; idx < length; idx += 1){ - inspectNode(object[objectKeyList[idx]], objects, duplicatesIndexes); - } - } - } - } -} -function getDuplicateReferences(object, state) { - const objects = [], duplicatesIndexes = []; - inspectNode(object, objects, duplicatesIndexes); - const length = duplicatesIndexes.length; - for(let index = 0; index < length; index += 1){ - state.duplicates.push(objects[duplicatesIndexes[index]]); - } - state.usedDuplicates = Array.from({ - length - }); -} -function dump(input, options) { - options = options || {}; - const state = new DumperState(options); - if (!state.noRefs) getDuplicateReferences(input, state); - if (writeNode(state, 0, input, true, true)) return `${state.dump}\n`; - return ""; -} -function stringify(obj, options) { - return dump(obj, options); -} -const mod1 = { - parse: parse, - parseAll: parseAll, - stringify: stringify, - Type: Type, - CORE_SCHEMA: core, - DEFAULT_SCHEMA: def, - EXTENDED_SCHEMA: extended, - FAILSAFE_SCHEMA: failsafe, - JSON_SCHEMA: json -}; -function incrementLastNumber(list) { - const newList = [ - ...list - ]; - newList[newList.length - 1]++; - return newList; -} -class EmVer { - values; - static from(range) { - if (range instanceof EmVer) { - return range; - } - return EmVer.parse(range); - } - static parse(range) { - const values = range.split(".").map((x)=>parseInt(x)); - for (const value of values){ - if (isNaN(value)) { - throw new Error(`Couldn't parse range: ${range}`); - } - } - return new EmVer(values); - } - constructor(values){ - this.values = values; - } - withLastIncremented() { - return new EmVer(incrementLastNumber(this.values)); - } - greaterThan(other) { - for(const i in this.values){ - if (other.values[i] == null) { - return true; - } - if (this.values[i] > other.values[i]) { - return true; - } - if (this.values[i] < other.values[i]) { - return false; - } - } - return false; - } - equals(other) { - if (other.values.length !== this.values.length) { - return false; - } - for(const i in this.values){ - if (this.values[i] !== other.values[i]) { - return false; - } - } - return true; - } - greaterThanOrEqual(other) { - return this.greaterThan(other) || this.equals(other); - } - lessThanOrEqual(other) { - return !this.greaterThan(other); - } - lessThan(other) { - return !this.greaterThanOrEqual(other); - } - compare(other) { - if (this.equals(other)) { - return "equal"; - } else if (this.greaterThan(other)) { - return "greater"; - } else { - return "less"; - } - } - compareForSort(other) { - return mod.matches(this.compare(other)).when("equal", ()=>0).when("greater", ()=>1).when("less", ()=>-1).unwrap(); - } -} -function migrationFn(fn) { - return fn; -} -function fromMapping(migrations, currentVersion) { - const directionShape = mod.literals("from", "to"); - return async (effects, version, direction)=>{ - if (!directionShape.test(direction)) { - return { - error: 'Must specify arg "from" or "to".' - }; - } - let configured = true; - const current = EmVer.parse(currentVersion); - const other = EmVer.parse(version); - const filteredMigrations = Object.entries(migrations).map(([version, migration])=>({ - version: EmVer.parse(version), - migration - })).filter(({ version })=>version.greaterThan(other) && version.lessThanOrEqual(current)); - const migrationsToRun = mod.matches(direction).when("from", ()=>filteredMigrations.sort((a, b)=>a.version.compareForSort(b.version)).map(({ migration })=>migration.up)).when("to", ()=>filteredMigrations.sort((a, b)=>b.version.compareForSort(a.version)).map(({ migration })=>migration.down)).unwrap(); - for (const migration of migrationsToRun){ - configured = (await migration(effects)).configured && configured; - } - return { - result: { - configured - } - }; - }; -} -const isType = mod.shape({ - type: mod.string -}); -const recordString = mod.dictionary([ - mod.string, - mod.unknown -]); -const matchDefault = mod.shape({ - default: mod.unknown -}); -const matchNullable = mod.shape({ - nullable: mod.literal(true) -}); -const matchPattern = mod.shape({ - pattern: mod.string -}); -const rangeRegex = /(\[|\()(\*|(\d|\.)+),(\*|(\d|\.)+)(\]|\))/; -const matchRange = mod.shape({ - range: mod.regex(rangeRegex) -}); -const matchIntegral = mod.shape({ - integral: mod.literal(true) -}); -const matchSpec = mod.shape({ - spec: recordString -}); -const matchSubType = mod.shape({ - subtype: mod.string -}); -const matchUnion = mod.shape({ - tag: mod.shape({ - id: mod.string - }), - variants: recordString -}); -const matchValues = mod.shape({ - values: mod.arrayOf(mod.string) -}); -function charRange(value = "") { - const split = value.split("-").filter(Boolean).map((x)=>x.charCodeAt(0)); - if (split.length < 1) return null; - if (split.length === 1) return [ - split[0], - split[0] - ]; - return [ - split[0], - split[1] - ]; -} -function generateDefault(generate, { random = ()=>Math.random() } = {}) { - const validCharSets = generate.charset.split(",").map(charRange).filter(Array.isArray); - if (validCharSets.length === 0) throw new Error("Expecing that we have a valid charset"); - const max = validCharSets.reduce((acc, x)=>x.reduce((x, y)=>Math.max(x, y), acc), 0); - let i = 0; - const answer = Array(generate.len); - while(i < generate.len){ - const nextValue = Math.round(random() * max); - const inRange = validCharSets.reduce((acc, [lower, upper])=>acc || nextValue >= lower && nextValue <= upper, false); - if (!inRange) continue; - answer[i] = String.fromCharCode(nextValue); - i++; - } - return answer.join(""); -} -function withPattern(value) { - if (matchPattern.test(value)) return mod.regex(RegExp(value.pattern)); - return mod.string; -} -function matchNumberWithRange(range) { - const matched = rangeRegex.exec(range); - if (!matched) return mod.number; - const [, left, leftValue, , rightValue, , right] = matched; - return mod.number.validate(leftValue === "*" ? (_)=>true : left === "[" ? (x)=>x >= Number(leftValue) : (x)=>x > Number(leftValue), leftValue === "*" ? "any" : left === "[" ? `greaterThanOrEqualTo${leftValue}` : `greaterThan${leftValue}`).validate(rightValue === "*" ? (_)=>true : right === "]" ? (x)=>x <= Number(rightValue) : (x)=>x < Number(rightValue), rightValue === "*" ? "any" : right === "]" ? `lessThanOrEqualTo${rightValue}` : `lessThan${rightValue}`); -} -function withIntegral(parser, value) { - if (matchIntegral.test(value)) { - return parser.validate(Number.isInteger, "isIntegral"); - } - return parser; -} -function withRange(value) { - if (matchRange.test(value)) { - return matchNumberWithRange(value.range); - } - return mod.number; -} -const isGenerator = mod.shape({ - charset: mod.string, - len: mod.number -}).test; -function defaultNullable(parser, value) { - if (matchDefault.test(value)) { - if (isGenerator(value.default)) return parser.defaultTo(parser.unsafeCast(generateDefault(value.default))); - return parser.defaultTo(value.default); - } - if (matchNullable.test(value)) return parser.optional(); - return parser; -} -function guardAll(value) { - if (!isType.test(value)) { - return mod.unknown; - } - switch(value.type){ - case "boolean": - return defaultNullable(mod.boolean, value); - case "string": - return defaultNullable(withPattern(value), value); - case "number": - return defaultNullable(withIntegral(withRange(value), value), value); - case "object": - if (matchSpec.test(value)) { - return defaultNullable(typeFromProps(value.spec), value); - } - return mod.unknown; - case "list": - { - const spec = matchSpec.test(value) && value.spec || {}; - const rangeValidate = matchRange.test(value) && matchNumberWithRange(value.range).test || (()=>true); - const { default: _, ...arrayOfSpec } = spec; - const subtype = matchSubType.unsafeCast(value).subtype; - return defaultNullable(mod.arrayOf(guardAll({ - type: subtype, - ...arrayOfSpec - })).validate((x)=>rangeValidate(x.length), "valid length"), value); - } - case "enum": - if (matchValues.test(value)) { - return defaultNullable(mod.literals(value.values[0], ...value.values), value); - } - return mod.unknown; - case "pointer": - return mod.unknown; - case "union": - if (matchUnion.test(value)) { - return mod.some(...Object.entries(value.variants).map(([variant, spec])=>mod.shape({ - [value.tag.id]: mod.literal(variant) - }).concat(typeFromProps(spec)))); - } - return mod.unknown; - } - return mod.unknown; -} -function typeFromProps(valueDictionary) { - if (!recordString.test(valueDictionary)) return mod.unknown; - return mod.shape(Object.fromEntries(Object.entries(valueDictionary).map(([key, value])=>[ - key, - guardAll(value) - ]))); -} -function unwrapResultType(res) { - if ("error-code" in res) { - throw new Error(res["error-code"][1]); - } else if ("error" in res) { - throw new Error(res["error"]); - } else { - return res.result; - } -} -const exists = (effects, props)=>effects.metadata(props).then((_)=>true, (_)=>false); -const errorCode = (code, error)=>({ - "error-code": [ - code, - error - ] - }); -const error = (error)=>({ - error - }); -const ok = { - result: null -}; -const isKnownError = (e)=>e instanceof Object && ("error" in e || "error-code" in e); -const asResult = (result)=>({ - result: result - }); -const noPropertiesFound = { - result: { - version: 2, - data: { - "Not Ready": { - type: "string", - value: "Could not find properties. The service might still be starting", - qr: false, - copyable: false, - masked: false, - description: "Fallback Message When Properties could not be found" - } - } - } -}; -const properties = async (effects)=>{ - if (await exists(effects, { - path: "start9/stats.yaml", - volumeId: "main" - }) === false) { - return noPropertiesFound; - } - return await effects.readFile({ - path: "start9/stats.yaml", - volumeId: "main" - }).then(mod1.parse).then(asResult); -}; -const setConfig = async (effects, newConfig, dependsOn = {})=>{ - await effects.createDir({ - path: "start9", - volumeId: "main" - }); - await effects.writeFile({ - path: "start9/config.yaml", - toWrite: mod1.stringify(newConfig), - volumeId: "main" - }); - const result = { - signal: "SIGTERM", - "depends-on": dependsOn - }; - return { - result - }; -}; -const { any: any1, string: string1, dictionary: dictionary1 } = mod; -const matchConfig = dictionary1([ - string1, - any1 -]); -const getConfig = (spec)=>async (effects)=>{ - const config = await effects.readFile({ - path: "start9/config.yaml", - volumeId: "main" - }).then((x)=>mod1.parse(x)).then((x)=>matchConfig.unsafeCast(x)).catch((e)=>{ - effects.info(`Got error ${e} while trying to read the config`); - return undefined; - }); - return { - result: { - config, - spec - } - }; - }; -const getConfigAndMatcher = (spec)=>[ - async (effects)=>{ - const config = await effects.readFile({ - path: "start9/config.yaml", - volumeId: "main" - }).then((x)=>mod1.parse(x)).then((x)=>matchConfig.unsafeCast(x)).catch((e)=>{ - effects.info(`Got error ${e} while trying to read the config`); - return undefined; - }); - return { - result: { - config, - spec - } - }; - }, - typeFromProps(spec) - ]; -function updateConfig(fn, configured, noRepeat, noFail = false) { - return migrationFn(async (effects)=>{ - await noRepeatGuard(effects, noRepeat, async ()=>{ - let config = unwrapResultType(await getConfig({})(effects)).config; - if (config) { - try { - config = await fn(config, effects); - } catch (e) { - if (!noFail) { - throw e; - } else { - configured = false; - } - } - unwrapResultType(await setConfig(effects, config)); - } - }); - return { - configured - }; - }); -} -async function noRepeatGuard(effects, noRepeat, fn) { - if (!noRepeat) { - return fn(); - } - if (!await exists(effects, { - path: "start9/migrations", - volumeId: "main" - })) { - await effects.createDir({ - path: "start9/migrations", - volumeId: "main" - }); - } - const migrationPath = { - path: `start9/migrations/${noRepeat.version}.complete`, - volumeId: "main" - }; - if (noRepeat.type === "up") { - if (!await exists(effects, migrationPath)) { - await fn(); - await effects.writeFile({ - ...migrationPath, - toWrite: "" - }); - } - } else if (noRepeat.type === "down") { - if (await exists(effects, migrationPath)) { - await fn(); - await effects.removeFile(migrationPath); - } - } -} -async function initNoRepeat(effects, migrations, startingVersion) { - if (!await exists(effects, { - path: "start9/migrations", - volumeId: "main" - })) { - const starting = EmVer.parse(startingVersion); - await effects.createDir({ - path: "start9/migrations", - volumeId: "main" - }); - for(const version in migrations){ - const migrationVersion = EmVer.parse(version); - if (migrationVersion.lessThanOrEqual(starting)) { - await effects.writeFile({ - path: `start9/migrations/${version}.complete`, - volumeId: "main", - toWrite: "" - }); - } - } - } -} -function fromMapping1(migrations, currentVersion) { - const inner = fromMapping(migrations, currentVersion); - return async (effects, version, direction)=>{ - await initNoRepeat(effects, migrations, direction === "from" ? version : currentVersion); - return inner(effects, version, direction); - }; -} -const mod2 = { - updateConfig: updateConfig, - noRepeatGuard: noRepeatGuard, - initNoRepeat: initNoRepeat, - fromMapping: fromMapping1 -}; -const mod3 = { - properties: properties, - setConfig: setConfig, - getConfig: getConfig, - getConfigAndMatcher: getConfigAndMatcher, - migrations: mod2 -}; -const checkWebUrl = (url)=>{ - return async (effects, duration)=>{ - let errorValue; - if (errorValue = guardDurationAboveMinimum({ - duration, - minimumTime: 5000 - })) return errorValue; - return await effects.fetch(url).then((_)=>ok).catch((e)=>{ - effects.warn(`Error while fetching URL: ${url}`); - effects.error(JSON.stringify(e)); - effects.error(e.toString()); - return error(`Error while fetching URL: ${url}`); - }); - }; -}; -const runHealthScript = ({ command, args })=>async (effects, _duration)=>{ - const res = await effects.runCommand({ - command, - args - }); - if ("result" in res) { - return { - result: null - }; - } else { - return res; - } - }; -const guardDurationAboveMinimum = (input)=>input.duration <= input.minimumTime ? errorCode(60, "Starting") : null; -const catchError = (effects)=>(e)=>{ - if (isKnownError(e)) return e; - effects.error(`Health check failed: ${e}`); - return error("Error while running health check"); - }; -const mod4 = { - checkWebUrl: checkWebUrl, - runHealthScript: runHealthScript, - guardDurationAboveMinimum: guardDurationAboveMinimum, - catchError: catchError -}; -const migration = mod3.migrations.fromMapping({}, "1.0.9"); -const health = { - "web-ui": mod4.checkWebUrl("http://my-speed.embassy:5216") -}; -export { migration as migration }; -export { health as health }; diff --git a/scripts/embassy.ts b/scripts/embassy.ts deleted file mode 100644 index c26f913..0000000 --- a/scripts/embassy.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { migration } from "./procedures/migrations.ts"; -export { health } from "./procedures/healthChecks.ts"; diff --git a/scripts/procedures/healthChecks.ts b/scripts/procedures/healthChecks.ts deleted file mode 100644 index 3381e16..0000000 --- a/scripts/procedures/healthChecks.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { types as T, healthUtil } from "../deps.ts"; - -export const health: T.ExpectedExports.health = { - "web-ui": healthUtil.checkWebUrl("http://my-speed.embassy:5216") -} diff --git a/scripts/procedures/migrations.ts b/scripts/procedures/migrations.ts deleted file mode 100644 index 464ae60..0000000 --- a/scripts/procedures/migrations.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { compat, types as T } from "../deps.ts"; - -export const migration: T.ExpectedExports.migration = compat.migrations - .fromMapping({}, "1.0.9" ); diff --git a/startos/actions/index.ts b/startos/actions/index.ts new file mode 100644 index 0000000..c82cb95 --- /dev/null +++ b/startos/actions/index.ts @@ -0,0 +1,3 @@ +import { sdk } from '../sdk' + +export const actions = sdk.Actions.of() diff --git a/startos/backups.ts b/startos/backups.ts new file mode 100644 index 0000000..0a90b1e --- /dev/null +++ b/startos/backups.ts @@ -0,0 +1,5 @@ +import { sdk } from './sdk' + +export const { createBackup, restoreInit } = sdk.setupBackups( + async ({ effects }) => sdk.Backups.ofVolumes('main'), +) 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/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts new file mode 100644 index 0000000..ad0ecf5 --- /dev/null +++ b/startos/i18n/dictionaries/default.ts @@ -0,0 +1,19 @@ +export const DEFAULT_LANG = 'en_US' + +const dict = { + // main.ts + 'Web Interface': 0, + 'The web interface is ready': 1, + 'The web interface is not ready': 2, + + // interfaces.ts + 'Web UI': 3, + 'The web interface of MySpeed': 4, +} 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..3ba5f67 --- /dev/null +++ b/startos/i18n/dictionaries/translations.ts @@ -0,0 +1,32 @@ +import { LangDict } from './default' + +export default { + es_ES: { + 0: 'Interfaz web', + 1: 'La interfaz web está lista', + 2: 'La interfaz web no está lista', + 3: 'Interfaz web', + 4: 'La interfaz web de MySpeed', + }, + de_DE: { + 0: 'Weboberfläche', + 1: 'Die Weboberfläche ist bereit', + 2: 'Die Weboberfläche ist nicht bereit', + 3: 'Weboberfläche', + 4: 'Die Weboberfläche von MySpeed', + }, + pl_PL: { + 0: 'Interfejs webowy', + 1: 'Interfejs webowy jest gotowy', + 2: 'Interfejs webowy nie jest gotowy', + 3: 'Interfejs webowy', + 4: 'Interfejs webowy MySpeed', + }, + fr_FR: { + 0: 'Interface web', + 1: "L'interface web est prête", + 2: "L'interface web n'est pas prête", + 3: 'Interface web', + 4: "L'interface web de MySpeed", + }, +} 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..5bc2685 --- /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 './install/versionGraph' +export const manifest = buildManifest(versionGraph, sdkManifest) diff --git a/startos/init/index.ts b/startos/init/index.ts new file mode 100644 index 0000000..f69244d --- /dev/null +++ b/startos/init/index.ts @@ -0,0 +1,16 @@ +import { sdk } from '../sdk' +import { setDependencies } from '../dependencies' +import { setInterfaces } from '../interfaces' +import { versionGraph } from '../install/versionGraph' +import { actions } from '../actions' +import { restoreInit } from '../backups' + +export const init = sdk.setupInit( + restoreInit, + versionGraph, + setInterfaces, + setDependencies, + actions, +) + +export const uninit = sdk.setupUninit(versionGraph) diff --git a/startos/install/versionGraph.ts b/startos/install/versionGraph.ts new file mode 100644 index 0000000..1016a54 --- /dev/null +++ b/startos/install/versionGraph.ts @@ -0,0 +1,8 @@ +import { VersionGraph } from '@start9labs/start-sdk' +import { current, other } from './versions' + +export const versionGraph = VersionGraph.of({ + current, + other, + preInstall: async (effects) => {}, +}) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts new file mode 100644 index 0000000..40f0bfb --- /dev/null +++ b/startos/install/versions/index.ts @@ -0,0 +1,2 @@ +export { v1_0_9_1_b0 as current } from './v1.0.9.1.b0' +export const other = [] diff --git a/startos/install/versions/v1.0.9.1.b0.ts b/startos/install/versions/v1.0.9.1.b0.ts new file mode 100644 index 0000000..8878adb --- /dev/null +++ b/startos/install/versions/v1.0.9.1.b0.ts @@ -0,0 +1,16 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +export const v1_0_9_1_b0 = VersionInfo.of({ + version: '1.0.9:1-beta.0', + releaseNotes: { + en_US: 'Revamped for StartOS 0.4.0', + es_ES: 'Renovado para StartOS 0.4.0', + de_DE: 'Überarbeitet für StartOS 0.4.0', + pl_PL: 'Przeprojektowany dla StartOS 0.4.0', + fr_FR: 'Refait pour StartOS 0.4.0', + }, + migrations: { + up: async ({ effects }) => {}, + down: async ({ effects }) => {}, + }, +}) diff --git a/startos/interfaces.ts b/startos/interfaces.ts new file mode 100644 index 0000000..c77bcc4 --- /dev/null +++ b/startos/interfaces.ts @@ -0,0 +1,25 @@ +import { i18n } from './i18n' +import { sdk } from './sdk' +import { uiPort } from './utils' + +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('Web UI'), + id: 'ui', + description: i18n('The web interface of MySpeed'), + 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..76ba5c1 --- /dev/null +++ b/startos/main.ts @@ -0,0 +1,32 @@ +import { i18n } from './i18n' +import { sdk } from './sdk' +import { uiPort } from './utils' + +export const main = sdk.setupMain(async ({ effects }) => { + console.info('Starting MySpeed...') + + return sdk.Daemons.of(effects).addDaemon('primary', { + subcontainer: await sdk.SubContainer.of( + effects, + { imageId: 'main' }, + sdk.Mounts.of().mountVolume({ + volumeId: 'main', + subpath: null, + mountpoint: '/myspeed/data', + readonly: false, + }), + 'main', + ), + exec: { command: sdk.useEntrypoint(), runAsInit: true }, + ready: { + display: i18n('Web Interface'), + gracePeriod: 30_000, + fn: () => + sdk.healthCheck.checkPortListening(effects, uiPort, { + successMessage: i18n('The web interface is ready'), + errorMessage: i18n('The web interface is not ready'), + }), + }, + requires: [], + }) +}) diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts new file mode 100644 index 0000000..05fa175 --- /dev/null +++ b/startos/manifest/i18n.ts @@ -0,0 +1,24 @@ +export const short = { + en_US: 'Speed test analysis software that records your internet speed', + es_ES: + 'Software de análisis de velocidad que registra la velocidad de tu internet', + de_DE: + 'Geschwindigkeitstest-Analysesoftware, die Ihre Internetgeschwindigkeit aufzeichnet', + pl_PL: + 'Oprogramowanie do analizy prędkości internetu, które rejestruje wyniki testów', + fr_FR: + "Logiciel d'analyse de vitesse qui enregistre la vitesse de votre connexion internet", +} + +export const long = { + en_US: + 'MySpeed is a speed test analysis software that stores the speed of your internet for up to 30 days. It automatically creates tests every hour, which you can view in the dashboard. Choose between Ookla, LibreSpeed, and Cloudflare speed test servers.', + es_ES: + 'MySpeed es un software de análisis de pruebas de velocidad que almacena la velocidad de tu internet durante un máximo de 30 días. Crea pruebas automáticamente cada hora, que puedes ver en el panel de control. Elige entre servidores de prueba de velocidad Ookla, LibreSpeed y Cloudflare.', + de_DE: + 'MySpeed ist eine Geschwindigkeitstest-Analysesoftware, die die Geschwindigkeit Ihres Internets bis zu 30 Tage lang speichert. Es erstellt automatisch stündlich Tests, die Sie im Dashboard einsehen können. Wählen Sie zwischen Ookla-, LibreSpeed- und Cloudflare-Geschwindigkeitstestservern.', + pl_PL: + 'MySpeed to oprogramowanie do analizy testów prędkości, które przechowuje wyniki prędkości internetu przez okres do 30 dni. Automatycznie tworzy testy co godzinę, które można przeglądać w panelu. Wybierz między serwerami testów prędkości Ookla, LibreSpeed i Cloudflare.', + fr_FR: + "MySpeed est un logiciel d'analyse de tests de vitesse qui enregistre la vitesse de votre connexion internet pendant 30 jours maximum. Il crée automatiquement des tests toutes les heures, consultables depuis le tableau de bord. Choisissez entre les serveurs de test Ookla, LibreSpeed et Cloudflare.", +} diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts new file mode 100644 index 0000000..2410227 --- /dev/null +++ b/startos/manifest/index.ts @@ -0,0 +1,23 @@ +import { setupManifest } from '@start9labs/start-sdk' +import { short, long } from './i18n' + +export const manifest = setupManifest({ + id: 'my-speed', + title: 'MySpeed', + license: 'MIT', + wrapperRepo: 'https://github.com/Start9Labs/myspeed-startos', + upstreamRepo: 'https://github.com/gnmyt/myspeed', + supportSite: 'https://github.com/gnmyt/myspeed/issues', + marketingSite: 'https://myspeed.dev/', + donationUrl: 'https://ko-fi.com/gnmyt', + docsUrl: 'https://docs.myspeed.dev/', + description: { short, long }, + volumes: ['main'], + images: { + main: { + source: { dockerBuild: { dockerfile: 'Dockerfile', workdir: '.' } }, + 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..be6a965 --- /dev/null +++ b/startos/utils.ts @@ -0,0 +1 @@ +export const uiPort = 5216 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a2945a5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": ["startos/**/*.ts", "node_modules/**/startos"], + "compilerOptions": { + "target": "ES2018", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true + } +} From 95f7d378736eea8921d2e8219b8fa6332e3483c0 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 12 Feb 2026 22:40:35 -0700 Subject: [PATCH 02/13] readme --- README.md | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3f6770 --- /dev/null +++ b/README.md @@ -0,0 +1,149 @@ +

+ MySpeed Logo +

+ +# MySpeed on StartOS + +> **Upstream docs:** +> +> Everything not listed in this document should behave the same as upstream +> MySpeed v1.0.9. If a feature, setting, or behavior is not mentioned +> here, the upstream documentation is accurate and fully applicable. + +MySpeed is a speed test analysis software that records your internet speed for up to 30 days. It automates speed tests using Cron expressions and generates clear statistics on speed, ping, and more. See the [upstream repo](https://github.com/gnmyt/myspeed) for general MySpeed documentation. + +--- + +## Table of Contents + +- [Image and Container Runtime](#image-and-container-runtime) +- [Volume and Data Layout](#volume-and-data-layout) +- [Installation and First-Run Flow](#installation-and-first-run-flow) +- [Configuration Management](#configuration-management) +- [Network Access and Interfaces](#network-access-and-interfaces) +- [Actions](#actions-startos-ui) +- [Backups and Restore](#backups-and-restore) +- [Health Checks](#health-checks) +- [Dependencies](#dependencies) +- [Limitations and Differences](#limitations-and-differences) +- [What Is Unchanged from Upstream](#what-is-unchanged-from-upstream) +- [Contributing](#contributing) +- [Quick Reference for AI Consumers](#quick-reference-for-ai-consumers) + +--- + +## Image and Container Runtime + +| Property | Value | +|----------|-------| +| Image | Custom Dockerfile extending `germannewsmaker/myspeed:1.0.9` | +| Architectures | x86_64, aarch64 | +| Entrypoint | `tini -- node server` | + +The custom Dockerfile adds [tini](https://github.com/krallin/tini) as an init process to handle SIGTERM signals for clean shutdown. The upstream image does not handle signals gracefully on its own. + +## Volume and Data Layout + +| Volume | Mount Point | Purpose | +|--------|-------------|---------| +| `main` | `/myspeed/data` | All MySpeed data (SQLite database, configuration) | + +## Installation and First-Run Flow + +1. MySpeed starts with a fresh SQLite database +2. On first visit to the web UI, a **welcome dialog** is displayed where you can configure initial settings and set a password +3. Speed tests begin automatically based on the default Cron schedule + +No StartOS-specific setup steps are required. All configuration is done through MySpeed's own web UI. + +## Configuration Management + +All MySpeed settings are managed through the **upstream web UI** — there are no StartOS-managed settings or actions. + +Settings available in the MySpeed UI include: + +| Category | Settings | +|----------|----------| +| **Speed Test** | Test provider (Ookla, LibreSpeed, Cloudflare), server selection, Cron schedule, data retention period | +| **Network** | Interface selection for speed tests | +| **Notifications** | Discord, Gotify, Pushover, Telegram, webhooks, health check monitoring | +| **Monitoring** | Prometheus metrics endpoint, Grafana integration | +| **Security** | Admin password | + +## Network Access and Interfaces + +| Interface | Port | Protocol | Purpose | +|-----------|------|----------|---------| +| Web UI | 5216 | HTTP | MySpeed dashboard and settings | + +## Actions (StartOS UI) + +None. MySpeed is fully managed through its own web interface. + +## Backups and Restore + +**Backed up:** The entire `main` volume, including the SQLite database and all configuration. + +**Restore behavior:** Restoring overwrites current data with the backup copy, including speed test history and settings. + +## Health Checks + +| Check | Method | Grace Period | Messages | +|-------|--------|-------------|----------| +| **Web Interface** | `checkPortListening` on port 5216 | 30 seconds | Ready: "The web interface is ready" | + +The 30-second grace period accommodates MySpeed's startup time, during which it loads integrations and performs network discovery. + +## Dependencies + +None. MySpeed is a standalone service. + +## Limitations and Differences + +1. **Custom Dockerfile with tini** — adds an init process for proper signal handling; the upstream image does not handle SIGTERM gracefully +2. **No StartOS-managed configuration** — all settings (password, test schedule, notifications, etc.) are configured through MySpeed's own web UI +3. **No password reset action** — if the admin password is forgotten, the only recovery method is clearing it from the SQLite database +4. **Speed test accuracy** — tests run through the container's network stack, which may differ slightly from bare-metal results +5. **Subsequent startups may be slow** — MySpeed performs network discovery and integration loading on each start, which can take 15–25 seconds + +## What Is Unchanged from Upstream + +- Speed test execution (Ookla, LibreSpeed, Cloudflare) +- Statistics dashboard and data visualization +- Cron-based test scheduling +- Multi-server support +- Notification integrations (Discord, Gotify, Pushover, Telegram, webhooks) +- Health check monitoring (email, Signal, WhatsApp, Telegram) +- Prometheus metrics endpoint +- Grafana integration +- Admin password protection +- Data retention settings +- SQLite database storage + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for build instructions and development workflow. + +--- + +## Quick Reference for AI Consumers + +```yaml +package_id: my-speed +upstream_version: "1.0.9" +image: custom Dockerfile (germannewsmaker/myspeed:1.0.9 + tini) +architectures: [x86_64, aarch64] +volumes: + main: /myspeed/data +ports: + ui: 5216 +dependencies: none +startos_managed_files: none +actions: none +health_checks: + - checkPortListening:5216: web_interface (30s grace period) +backup_volumes: + - main (full volume) +configuration: upstream web UI only +auth: password set via upstream web UI welcome dialog +``` From c4f454d23b31600051e606009a8a964e489157b2 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Wed, 4 Mar 2026 16:06:30 -0700 Subject: [PATCH 03/13] beta 55 --- .github/workflows/buildService.yml | 4 +- README.md | 2 +- icon.png | Bin 6485 -> 0 bytes icon.svg | 10 +++++ package-lock.json | 38 ++++++++++++------ package.json | 2 +- startos/install/versionGraph.ts | 1 - startos/install/versions/index.ts | 2 +- .../{v1.0.9.1.b0.ts => v1.0.9.2.b0.ts} | 4 +- startos/manifest/index.ts | 11 +++-- 10 files changed, 48 insertions(+), 26 deletions(-) delete mode 100644 icon.png create mode 100644 icon.svg rename startos/install/versions/{v1.0.9.1.b0.ts => v1.0.9.2.b0.ts} (84%) diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml index feb2ae9..00ab89b 100644 --- a/.github/workflows/buildService.yml +++ b/.github/workflows/buildService.yml @@ -4,10 +4,10 @@ on: workflow_dispatch: pull_request: paths-ignore: ['*.md'] - branches: ['main', 'master', 'update/040'] + branches: ['master'] push: paths-ignore: ['*.md'] - branches: ['main', 'master', 'update/040'] + branches: ['master'] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} diff --git a/README.md b/README.md index f3f6770..5cce77d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- MySpeed Logo + MySpeed Logo

# MySpeed on StartOS diff --git a/icon.png b/icon.png deleted file mode 100644 index 30866cba3d83b2835079339ea9da329ff217c33d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6485 zcmcIphgTEL+g*r6N`fLtN02H-dXwIZN)c%yy|)O6BAw8sqkuH&#ZU!lQUjv&5+L*< zzET3x2?PRxFTX$H+nsY}&z$qjJUeIS-uv8*Gt}3HQZiEl007n1(J%r465=BXKu${R zrfw^X5j(K`6KxIP`hRy}=a)19V4~F3P&M{n*v)e>HZfc2%NE(6J(u<99L{EZQY}_o zjF6;}=R^>%!a0^x>-vNU%2U$0y70R+?Q@1ZY%B=wDRKV&r{dY!O)LFNkZ0TToB3_y z4Kw?_)!eaVJ6!K-a$;e5sG)2m+P2w9A8y?oTkgb_-FyA@V{yy1!rKk~V6}yAOk7fK z%U``Bk~jTG*tqW0C%X`%gCqIp8a5O%UUzHs^!o$jyui#VPeN^aY5ygyAIcwjA?tH6 z!|Ut+kiEr!d4;i_6ZtJEV8dH)kgwlTnyxgXLSC%UUke^%rtM0cLrsx)_j#d(>1_oU zKlLqw507Ybx4VSMW$r_!J8^Z&XwM%PPe8^xhW+F>*x$zgF%R#!RNJw0pY<9H(gOu+ zWTFtg{k4OY7e=cib|*PjqaE@`UGmGo+^QCYG1vFBR=w0lAij+^Ktx?@OiBf*`d7p< za7Se;Ob37E>raUS#yB}1Tpk>HP-lixSoIdW0bfvcK7fA0O2-AmTN~2mUy|_4V56$R zDE=p@5n8`8Nn`sZ;cI7LKW$##ChJd$y_syGvK*}>sZG*BD>*-8lo`K141O?~o8q+o z=eB^)^4_oEmXWM9I3!xEo+@?hk?TR}m4kzNnnT3%k{@?s`+kmfEBBMI-q}grZ?!wV zUn*gnlDytgGZQ5x+I3WXt%TBbvke=^rj8zO|ZYFE9??1ube zd-s24MX#^^mTWhvI}af?(4puR!1tn3W%uBw$bMUkJ_Le4WIoGQnjY)nn%wTfU9}MCX1^- z{B@yIJ+~aXQm-axXVrtKpJ8}}w)Ygz9L_5=$bSFSw=Tv{YlASPrj5BRY!jCFeuEW8 z@!+<5RNw(({&jpa3ZyZb7LMqsz0Mf>#lm2c?0~h$8D?NVwG~;YErlVz6xMiC`xn34 z3)li~vcB!x2#bm@2;t%EvyP{VHlvvBFje$o?smK+4RSSaG{K%~NA146h}|YMy?k|x z-wAG7aeu>FcjwE*q#EY6U6Cdzi-Nw%{GbEuvoL14DEIWWVT1qi_ z#n`eXbuZEl@MTo+WWG0-gSfWU5|&e9yd-!u9lsKl1<+f6j$_-L_xS6*wD>=_J&7%O z#oT=(RTf_Cg)0l&8hTDQET=%9yjmeYYj{qb3r5BG(EhzW>x<;3l*!jt^LZlO7Qo{p zs{&%|$G)gq3OnG61KM5kBZbWydk6_}>LBKB>FxezyP?Dk&)cN*50^^^soi&?q9x%p z5O&4>(TrQ9<#}pgLp*aOdsK8k3TTty2#m-rOANy0)yxj0Oy2ar>F)RXXpiqDJH=uz zFfjC*Z}8?=wn9j2?;?9Tyn-y}2Fh|rSGiXd(bc#+*Pr(a<_dBBCmP+%@y^1ELRE84 z@xA9;P~kCQH)vaeP1LW<>Pr_oWU!~TSyOHCd5XkcHEq-kBmY^Y*Z6uBnRSiabDXf; zo2{?A=8+w_UwP)9znBHgzd3|d;7gxuk;6v0(1Rih~{4JN`6p)d~(n&h;K%ulgW zGF`Xs5p;ib_J}c6a`NNvSe(C`G9{2dxD}sW+=%%CGeA$>_o3pFeE3q=^AU(cSJi6M zqC+euB}a22b9HyoB%q2sS|T$hCBJ5^5_zDa4i1xu=A2n8|jb+W+nN6^+A& z5f(`=BTezs@D+4AMG!(}cHTM{Xa_Q#V9#dA)BqVXSGzLQ)+XK0p)|tsaLDxJ9r(0v zvn%^No90nE=@4XZj6_bAjC(T|2BYRxmtEzsh+>Sgv7PCuUQGeP=P@c5(#wNTF!#KlLC_qFs3mF$G2j9|&W z;rQ~*;$wMhX31`m!XH+es0QBwmF+GKLRN6)VMExAKy*Pep+0n<{u~OA61eNY;XH2h zWH}RI<4RV1Bk|bqimb!J&*+0=Jl(tAHqjRg07kybzL6p!@Cmx0&ab6;TiZ)vf>to9 z$RrrmobI46qe4|+JJGF7X?bv6j5XO&x@+oR@$a&OYd0$XcG{NiTV591*%j7_s+b88 zbcj?9)#8(dj*5KN$hG6#mUs5-Q!(&b7B_S{E(WLlE z*!uk2Z%iuK0q~&%ou45da0}bR^<5wW_ZEEh8iD9o5;j_CJ<)BIpUEuEu|4LAD#|uF#dud>A6MnuP|8G9;KCTT7l&P9Crn z+qGdzpm9}ME`K>dg;Q}Dsc!?VajmwtbT!r>p$g-LU7g`-yQ5#mRchc@L2!w|V>z&c^NC zl#gd*FXyih25lsbl}}M~x-18y&k)=>tFFO*sT1Jtida{w9B>kWttwQ~6lF?~MNsa` z|EL>!Izba7k-=lTI0rCl_s=2Ka+)z1eH-*^UoXh3&-EJKc*fIp@VjJDB9}aRe|n}} zmL)NrqJ9gZ`tpQDd1yVs8Ky}^fUHomxWkzumZ_idZah7ibwe@Rss5!}JmKKct9~pI z@R!653ePT8oBffUREx6UJI0tKQ7w*ouuMq_Szt%{2hbdUA#mxC;?`T3gFlidT(c&N zKm4L$Y;SmQ+Y&|liDPwlm~Cg4G>WR~e1`AetPGoK?)_MS%<*qgM$0^Lg->nnQCN{x zHQkmd{O+@i4_PQbV_`QzbDxFBbjAFNfQSQ}fok(!6kRb~sc9;&I5a zX0`()6*te#S`q&$OatuNo%m$zO2*qfGn^HhShb-B^1Ri*lrxAn+lMqLumg41+bHLa?U1xnzjoKjn+C3NVv`F6PVq zZ1gEuB@I&aTL}00xnoR`;J27Ey~e#oQ>55lRzsR-@Cs^y=NZ; zBIgOWeO#Kpt?g;OSkZ|QbZ{K2d!fxf|ELA#1?;SAYAHECb^~C~Lk{wPzM3r0Bo%uV zo|78kwF3i;8_8jT)u|n|j~)`#G+YKuNUzYVc2k~K`@C;}2yRXmmTo={W6Yl4HZy+# ziSY=$QiIT|Jzjr7rYdi}uFMo5DJ`vHKF(Nl#KZ5wA!73orB7(!`OdL*b6oIxnyBKN z>q!i!g^h1=J46Rb=*=jb<8HA?O@)8`=|jt~Fb81V5tm=`WdqceK!wby<5xy1Q$t}v zOCOVn)BYzHPLYqenK=$*J=)x00R(#|{%#Jx(c|Ro7q~w6_=qFMGu{{$auwQL66=O| z&^3FIJ8$t{pX=}tSB=`>fHEmrJqD5Ig?w^LYBT7fiWoc^`U>O>65|lNcixA`KvE|G zwW7LPJ-40sC3@?Hj6-Q&kF7M-lcX*bUVd>GFQ3YJuYco)YYDf91)X6&-FuF2Nl*@B z5=kd~JW*Cr$L+|&yfe4dS0uvxy)VydJKMqYgitQpJ-43U={E61%HI9>pUtEErranr zZu`>aa$B8?OH z{zB3-R7~{XsNWHfzd*ZHDu||rZPpNTke(NC@DA7x8&4>W5%&cDaH#A!^mmc=TGotU zf7xE{qOX!W>;wTOLkNeKW(Z0JYT{}i>hrIK_0M3MVE^1R`A0KdI=dG20%c3X5q~2T zw_H4pS4=c#TtdFSp5Eugp`q(~;xJEu4)#Dw4~4*sRKZlU;JGRHNoFMJ;;##tP&gl8+iHh zf}dClwAy$&fNV+lY~d~j2{LE_7ed2ESo@8pqEgRKWahoc@ULMnPWEvpIDB=*5r(|l z-T}1KVZu#!*%gFyT8N*kkx+j;Cn{Ci)N<>@aTsc#H;&MHSj^JIh_#WM#^D?LF2c$> z%yu&Vi4fm9I4FGeU1-tDy7EuXLR`&J@;hni?s=+P8sf;m2nZj%d3?ykM@VqI4=fwJ zlXFzCpe*p|KW|bssXOFQB7*a`>kC|0xN_?G$cDoUz5g|WCuae*1_P{c2#4R(6svp4 z-afE-#DqM}D<$x5Kega6<>f&Z+$On&yq34X;f!ZsoZVZa+H+XsdD3ZR_lb||HFdUFYF5+mER(Vjc zZO!R0iu)$GjqjusMafJln~s-@r*w1lE1@Mg+{7~Qz`c2_;-(l|(T78D;_%Eepr)sa z9Sh+pZwMR}UJo?SPIV6gJuPT@?8?I7<3-Myb5EMrFMJmYf8?SD#>jiKnsCA*utfAt z;yg(XCt~YUA6juZiI<{1{re+|JgN{=WX3MkP_ORMM)om|pYFwr+|RX^6s=|jOa~c> zqTPF)U!s}+%o0BH*uLhJWN{uoE&0d@oIh2o;-ClXP&k)S3<7bE#37JkbZEd)A1FN` z_-R3>g$`Jijf-0hSXd?3)9H8X-VfX3&~^L|oh?5k1rIgvd((F+y3tRSGz<=B3w$mn z9xkp^1R%s%fWHuqHmC{@RFjMLxl1kG9k^XXDpVYp@l``!6D^14?_XWL;fb@(D{K=0 zo7cn35mdf9YE4nxoiwJ^?8a5(D$4b;e~!W4lJbi5DtQSk8+P~sMbS-^Dy9FM#Xhe% zX#r$HO>Ivsf>uRYBB7fikM+&lgMzjX)w5e=$F&Iy$7a7hq(~OYoXJ;5*6jDvxca}A z6ad8=Ss!Er@JB^lF01lyAjv-~I49#^UqXun{Aayy_jcvR+7)mcbKF8{&JX0S%@-@) z8zJvhT_^e#MeJ!P9je~$DH2Zw~IyWF4dH{Ox3*P5+!O{7CuFAnE`&FPU=7@4tr zq^TO{&A#GOi~t99;cvEIml+3LT&ai_BAsjS5&e~!2n(}6(2h)Mc4RZLn9TOQQ#bSD zB^vu&)41`0R}_YsD&eFw3fvuSsKnS$_NvIVfSRq4^2eHa z$LNbX>Umiu*bl6513oo;f;PGf<~Ii8B5c{M#ljYH?!S2`PHp2Sh~2mwl~4L_W?wzQ zNWqMi2S3Cgc)=wOGkWJTp7N4i%2B+s5gRn`e6Q8M2W0CDfs@B!Qr{H74WCVLMSNxM4ztc3&t|dDxr5T>FXWN1qq8QzQ&-?MxPt88kAL`cdW{D-1 zMEWw^JJS7n_tw2@CNEn^T^zFdy1i2UB@-A08tTPT}*t3Fuv$nT6cV< zVBL@TPp=^!mfI#?D3rnlmM!W?D=C)&C2SeTe*2=YeCa=!YN9kS7811*2mC`DwXusi z@AV`u5nZHGVLfbbT$;@7%07BhT;ozuQbm?p7_EatqVoEIsFEm zNEy3AO06`xaC`Y@l_6O%Iy@I6lWPaFo+um>*^|NkczKaLk)-b-t<}H(y!&Xtw?`rO z^#Q(*lOD#neij4Elxx2{ zyxbU|qfMho$7K}JXiDpQqVV+6leOaD^!PC4{vB#5K<(G;k+sncedo=5R^Nsu#Sw!4 z#8{CAZz?_}LSCCi7+8TWg zR`CXEJ|OO%tL!=Cfot60m8vcs5e&bdiG4g_6Sv){;06O2scvdz@$hqC#yCe4deBvN zFa$#)Z%t>OfT6+kFov0C$zt@SFx}#&zeCw@3&Wm~#FlVK+uu{Z!|d$9%Z} zksB!@`*zb==)JL(Wflpgx0{2 z*Cs@Bk>V#;Br)Fvm%O1b!byLytwBkh5o?CY++gp!v?5NbomZaop^eVV^pgWx_dwq< zGOm{mo4<|90}T(3+7U(1xoH!Ld(;)aJQ4UYaQYMRm<3+=Wf(lX!`v+ueTRv5q;N){ zf!#{b1MMbUX1pe;qFd0qh+BEqvquyuFmwOJpPV1&fx=f@HlHVtyUmv)!(c~BRL$Rt zNustUVTOCtF6c&7Bs*WCJ}QO_a-gJRvFKrKIa&21%y7xX^xN0ROZ_uX@IVE{ovO&3WEU9GHC<(wsw`vXEdt}8LG zkuluCMWCk$d?uPrm;G=o{i>HQOx5bc(*UR>e5i$lIg4#*T=w=$WojhLbB|OKnv-vn-$G! z^?QufUoybT_C|hi{Z1w@y8=((&Nm-~<7&Sh8Ky5w>XK|9i0BZ-A73f_*^fVNe-HzrS+W zmh>FGPy=N}nhom{O6)&)M0M54TB@(7>yMua&f4rOr8RgTYGD&_6y#2#vEaG~2&R3s z)SW>x+AqRZ^hh?rBP?k1+cz)kwj=Bq!M=Ke#1A3t#3eI$VsN`^u>D+ab7wh{sddpP zxd^nj6X0R5H9}6E6w~iH>z4-Ri28qPYi3vM@`3cUbK5OZ#Q!9KuBN_5t(tAb{{g>c BR0aS5 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..a1cb856 --- /dev/null +++ b/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index bb3bb44..9df25a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.48" + "@start9labs/start-sdk": "^0.4.0-beta.55" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.48", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.48.tgz", - "integrity": "sha512-beMdwhUffhnbSm3FgkWPJjAWazMhNMzqbHtS6yK2hX6VpP39JxzDVo8vEbJAUfot6LPw+OhOUtPEu6KDaGre6A==", + "version": "0.4.0-beta.55", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.55.tgz", + "integrity": "sha512-vPieBCBIhlSZDTtmDS8RDfC8gjJKaj2LtJThz/QGXN/NhFRUxUcVKBNL51S7MbOyJS7IYM/XU40Jqawdq2mqEw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -62,8 +62,9 @@ "ini": "^5.0.0", "isomorphic-fetch": "^3.0.0", "mime": "^4.0.7", - "ts-matches": "^6.3.2", - "yaml": "^2.7.1" + "yaml": "^2.7.1", + "zod": "^4.3.6", + "zod-deep-partial": "^1.2.0" } }, "node_modules/@types/ini": { @@ -186,12 +187,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/ts-matches": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.5.0.tgz", - "integrity": "sha512-MhuobYhHYn6MlOTPAF/qk3tsRRioPac5ofYn68tc3rAJaGjsw1MsX1MOSep52DkvNJPgNV0F73zfgcQfYTVeyQ==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -249,6 +244,25 @@ "funding": { "url": "https://github.com/sponsors/eemeli" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "peer": true, + "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 index d95700a..44ed78e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.48" + "@start9labs/start-sdk": "^0.4.0-beta.55" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/install/versionGraph.ts b/startos/install/versionGraph.ts index 1016a54..5a4fad7 100644 --- a/startos/install/versionGraph.ts +++ b/startos/install/versionGraph.ts @@ -4,5 +4,4 @@ import { current, other } from './versions' export const versionGraph = VersionGraph.of({ current, other, - preInstall: async (effects) => {}, }) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 40f0bfb..bbc15da 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,2 +1,2 @@ -export { v1_0_9_1_b0 as current } from './v1.0.9.1.b0' +export { v1_0_9_2_b0 as current } from './v1.0.9.2.b0' export const other = [] diff --git a/startos/install/versions/v1.0.9.1.b0.ts b/startos/install/versions/v1.0.9.2.b0.ts similarity index 84% rename from startos/install/versions/v1.0.9.1.b0.ts rename to startos/install/versions/v1.0.9.2.b0.ts index 8878adb..8ebae08 100644 --- a/startos/install/versions/v1.0.9.1.b0.ts +++ b/startos/install/versions/v1.0.9.2.b0.ts @@ -1,7 +1,7 @@ import { VersionInfo } from '@start9labs/start-sdk' -export const v1_0_9_1_b0 = VersionInfo.of({ - version: '1.0.9:1-beta.0', +export const v1_0_9_2_b0 = VersionInfo.of({ + version: '1.0.9:2-beta.0', releaseNotes: { en_US: 'Revamped for StartOS 0.4.0', es_ES: 'Renovado para StartOS 0.4.0', diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 2410227..17524a7 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -1,21 +1,20 @@ import { setupManifest } from '@start9labs/start-sdk' -import { short, long } from './i18n' +import { long, short } from './i18n' export const manifest = setupManifest({ id: 'my-speed', title: 'MySpeed', license: 'MIT', - wrapperRepo: 'https://github.com/Start9Labs/myspeed-startos', + packageRepo: 'https://github.com/Start9Labs/myspeed-startos/tree/update/040', upstreamRepo: 'https://github.com/gnmyt/myspeed', - supportSite: 'https://github.com/gnmyt/myspeed/issues', - marketingSite: 'https://myspeed.dev/', + marketingUrl: 'https://myspeed.dev/', donationUrl: 'https://ko-fi.com/gnmyt', - docsUrl: 'https://docs.myspeed.dev/', + docsUrls: ['https://docs.myspeed.dev/'], description: { short, long }, volumes: ['main'], images: { main: { - source: { dockerBuild: { dockerfile: 'Dockerfile', workdir: '.' } }, + source: { dockerBuild: {} }, arch: ['x86_64', 'aarch64'], }, }, From 418ce45aca45050285be8dd01ea97a79d090110b Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 5 Mar 2026 23:31:15 -0700 Subject: [PATCH 04/13] Update start-sdk to beta.57 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9df25a2..d1d1174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.55" + "@start9labs/start-sdk": "^0.4.0-beta.57" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.55", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.55.tgz", - "integrity": "sha512-vPieBCBIhlSZDTtmDS8RDfC8gjJKaj2LtJThz/QGXN/NhFRUxUcVKBNL51S7MbOyJS7IYM/XU40Jqawdq2mqEw==", + "version": "0.4.0-beta.57", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.57.tgz", + "integrity": "sha512-hmyRUk0NlaMn60hxgfKBL8Ua64A9KqRpZQQw4VaBKIddaRGhGYN9raNS/0LpzJ4eSegTMBBqb+tkGYmPdNExcA==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -74,9 +74,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 44ed78e..2c4c930 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.55" + "@start9labs/start-sdk": "^0.4.0-beta.57" }, "devDependencies": { "@types/node": "^22.19.0", From 2ffcff95c62a98e6018b14508005ce06c9e7b37d Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 6 Mar 2026 00:19:36 -0700 Subject: [PATCH 05/13] Update start-sdk to beta.58 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1d1174..d93f3ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.57" + "@start9labs/start-sdk": "^0.4.0-beta.58" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.57", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.57.tgz", - "integrity": "sha512-hmyRUk0NlaMn60hxgfKBL8Ua64A9KqRpZQQw4VaBKIddaRGhGYN9raNS/0LpzJ4eSegTMBBqb+tkGYmPdNExcA==", + "version": "0.4.0-beta.58", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.58.tgz", + "integrity": "sha512-d8R+g9WNpdxvvPyBDL2VGVJjmhleIV0JEOKm28JrhnfmNSf9gOLY40QzUV1PuSCNXl50dYiom/SXab1q0faidw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index 2c4c930..a5cb58a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.57" + "@start9labs/start-sdk": "^0.4.0-beta.58" }, "devDependencies": { "@types/node": "^22.19.0", From 79efe5d3d81a4cf430d790fd135ea31aedd7f6b3 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 10 Mar 2026 17:07:23 -0600 Subject: [PATCH 06/13] Update start-sdk to beta.59 Co-Authored-By: Claude Opus 4.6 --- CONTRIBUTING.md | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- startos/install/versions/index.ts | 2 +- startos/install/versions/v1.0.9.2.b0.ts | 16 ---------------- startos/install/versions/v1.0.9.3.b0.ts | 16 ++++++++++++++++ 6 files changed, 23 insertions(+), 23 deletions(-) delete mode 100644 startos/install/versions/v1.0.9.2.b0.ts create mode 100644 startos/install/versions/v1.0.9.3.b0.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 663911b..cbc38a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Building and Development -See the [StartOS Packaging Guide](https://docs.start9.com/packaging-guide/) for complete environment setup and build instructions. +See the [StartOS Packaging Guide](https://docs.start9.com/packaging/) for complete environment setup and build instructions. ### Quick Start diff --git a/package-lock.json b/package-lock.json index d93f3ca..c75e7ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.58" + "@start9labs/start-sdk": "^0.4.0-beta.59" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.58", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.58.tgz", - "integrity": "sha512-d8R+g9WNpdxvvPyBDL2VGVJjmhleIV0JEOKm28JrhnfmNSf9gOLY40QzUV1PuSCNXl50dYiom/SXab1q0faidw==", + "version": "0.4.0-beta.59", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.59.tgz", + "integrity": "sha512-Cp8r+bg1feNAAHyWtND8kpbfrK3crL3WS6JCp1+LJdx8OvTTGXg/2znMOoAn+J9pBbAui5CtXiJigC1OaACYVw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index a5cb58a..247b32c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.58" + "@start9labs/start-sdk": "^0.4.0-beta.59" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index bbc15da..83f4f4b 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,2 +1,2 @@ -export { v1_0_9_2_b0 as current } from './v1.0.9.2.b0' +export { v_1_0_9_3_b0 as current } from './v1.0.9.3.b0' export const other = [] diff --git a/startos/install/versions/v1.0.9.2.b0.ts b/startos/install/versions/v1.0.9.2.b0.ts deleted file mode 100644 index 8ebae08..0000000 --- a/startos/install/versions/v1.0.9.2.b0.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { VersionInfo } from '@start9labs/start-sdk' - -export const v1_0_9_2_b0 = VersionInfo.of({ - version: '1.0.9:2-beta.0', - releaseNotes: { - en_US: 'Revamped for StartOS 0.4.0', - es_ES: 'Renovado para StartOS 0.4.0', - de_DE: 'Überarbeitet für StartOS 0.4.0', - pl_PL: 'Przeprojektowany dla StartOS 0.4.0', - fr_FR: 'Refait pour StartOS 0.4.0', - }, - migrations: { - up: async ({ effects }) => {}, - down: async ({ effects }) => {}, - }, -}) diff --git a/startos/install/versions/v1.0.9.3.b0.ts b/startos/install/versions/v1.0.9.3.b0.ts new file mode 100644 index 0000000..f83ed17 --- /dev/null +++ b/startos/install/versions/v1.0.9.3.b0.ts @@ -0,0 +1,16 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_9_3_b0 = VersionInfo.of({ + version: '1.0.9:3-beta.0', + releaseNotes: { + en_US: 'Update to StartOS SDK beta.59', + es_ES: 'Actualización a StartOS SDK beta.59', + de_DE: 'Update auf StartOS SDK beta.59', + pl_PL: 'Aktualizacja do StartOS SDK beta.59', + fr_FR: 'Mise à jour vers StartOS SDK beta.59', + }, + migrations: { + up: async ({ effects }) => {}, + down: async ({ effects }) => {}, + }, +}) From 71cd76128a131928f61061dbd5a2118db54f20e8 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 16 Mar 2026 13:45:54 -0600 Subject: [PATCH 07/13] Update start-sdk to beta.60 Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 8 ++++---- package.json | 2 +- startos/install/versions/index.ts | 2 +- startos/install/versions/v1.0.9.3.b0.ts | 16 ---------------- startos/install/versions/v1.0.9.3.b1.ts | 16 ++++++++++++++++ 5 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 startos/install/versions/v1.0.9.3.b0.ts create mode 100644 startos/install/versions/v1.0.9.3.b1.ts diff --git a/package-lock.json b/package-lock.json index c75e7ff..a6583cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.59" + "@start9labs/start-sdk": "^0.4.0-beta.60" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.59", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.59.tgz", - "integrity": "sha512-Cp8r+bg1feNAAHyWtND8kpbfrK3crL3WS6JCp1+LJdx8OvTTGXg/2znMOoAn+J9pBbAui5CtXiJigC1OaACYVw==", + "version": "0.4.0-beta.60", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.60.tgz", + "integrity": "sha512-eAjVo+bHaY3ji/zWmXdVJ4rMP7PxTOr4VGm0TAjqgF8uyf90LsV7LI9/H+tdfI2Vp3b/Ymvhp7uoodenjqaZWg==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index 247b32c..bd234d8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.59" + "@start9labs/start-sdk": "^0.4.0-beta.60" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 83f4f4b..570f09a 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,2 +1,2 @@ -export { v_1_0_9_3_b0 as current } from './v1.0.9.3.b0' +export { v_1_0_9_3_b1 as current } from './v1.0.9.3.b1' export const other = [] diff --git a/startos/install/versions/v1.0.9.3.b0.ts b/startos/install/versions/v1.0.9.3.b0.ts deleted file mode 100644 index f83ed17..0000000 --- a/startos/install/versions/v1.0.9.3.b0.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { VersionInfo } from '@start9labs/start-sdk' - -export const v_1_0_9_3_b0 = VersionInfo.of({ - version: '1.0.9:3-beta.0', - releaseNotes: { - en_US: 'Update to StartOS SDK beta.59', - es_ES: 'Actualización a StartOS SDK beta.59', - de_DE: 'Update auf StartOS SDK beta.59', - pl_PL: 'Aktualizacja do StartOS SDK beta.59', - fr_FR: 'Mise à jour vers StartOS SDK beta.59', - }, - migrations: { - up: async ({ effects }) => {}, - down: async ({ effects }) => {}, - }, -}) diff --git a/startos/install/versions/v1.0.9.3.b1.ts b/startos/install/versions/v1.0.9.3.b1.ts new file mode 100644 index 0000000..268c587 --- /dev/null +++ b/startos/install/versions/v1.0.9.3.b1.ts @@ -0,0 +1,16 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_9_3_b1 = VersionInfo.of({ + version: '1.0.9:3-beta.1', + releaseNotes: { + en_US: 'Update to StartOS SDK beta.60', + es_ES: 'Actualización a StartOS SDK beta.60', + de_DE: 'Update auf StartOS SDK beta.60', + pl_PL: 'Aktualizacja do StartOS SDK beta.60', + fr_FR: 'Mise à jour vers StartOS SDK beta.60', + }, + migrations: { + up: async ({ effects }) => {}, + down: async ({ effects }) => {}, + }, +}) From e24a194cbb0a8022770d3da2d4bfb8dc435a4f15 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 16 Mar 2026 23:22:58 -0600 Subject: [PATCH 08/13] Update start-sdk to beta.61 Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 3 +++ package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4e6a47b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +## How the upstream version is pulled +- Image is `dockerBuild` from root +- Check `Dockerfile` for upstream version reference (FROM or ARG) diff --git a/package-lock.json b/package-lock.json index a6583cc..82b962a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.60" + "@start9labs/start-sdk": "^0.4.0-beta.61" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.60", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.60.tgz", - "integrity": "sha512-eAjVo+bHaY3ji/zWmXdVJ4rMP7PxTOr4VGm0TAjqgF8uyf90LsV7LI9/H+tdfI2Vp3b/Ymvhp7uoodenjqaZWg==", + "version": "0.4.0-beta.61", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.61.tgz", + "integrity": "sha512-mrrn4utBkRAec4hpm0psjQ46XlhiepwPYhwpf7GrMwz4c5YvHWfSPeEmLbSGWOmooYdVD0WqGcCnsVBq2EhVYA==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index bd234d8..ca558e3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.60" + "@start9labs/start-sdk": "^0.4.0-beta.61" }, "devDependencies": { "@types/node": "^22.19.0", From da2b3825550526b9989cf5479b54831c95123f26 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 19 Mar 2026 18:18:49 -0600 Subject: [PATCH 09/13] Update start-sdk to beta.62 Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 71 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82b962a..464f505 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.61" + "@start9labs/start-sdk": "^0.4.0-beta.62" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.61", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.61.tgz", - "integrity": "sha512-mrrn4utBkRAec4hpm0psjQ46XlhiepwPYhwpf7GrMwz4c5YvHWfSPeEmLbSGWOmooYdVD0WqGcCnsVBq2EhVYA==", + "version": "0.4.0-beta.62", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.62.tgz", + "integrity": "sha512-D1e30kr7Faojxdakwi5FZf3vSVhjaPHXwFG5a6WY2t5cJL1TG+Gw8p9RrCj9Ttvgc63yxP/CrF1xPtEGFm9vSA==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -59,6 +59,7 @@ "@noble/hashes": "^1.7.2", "@types/ini": "^4.1.1", "deep-equality-data-structures": "^2.0.0", + "fast-xml-parser": "^5.5.6", "ini": "^5.0.0", "isomorphic-fetch": "^3.0.0", "mime": "^4.0.7", @@ -102,6 +103,41 @@ "object-hash": "^3.0.0" } }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.7.tgz", + "integrity": "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.1.3", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/ini": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", @@ -165,6 +201,21 @@ "node": ">= 6" } }, + "node_modules/path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/prettier": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", @@ -181,6 +232,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/strnum": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", diff --git a/package.json b/package.json index ca558e3..061d424 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.61" + "@start9labs/start-sdk": "^0.4.0-beta.62" }, "devDependencies": { "@types/node": "^22.19.0", From 5c351646c69810c93f17b7b1aafcb82aa6703fb9 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 19 Mar 2026 18:45:09 -0600 Subject: [PATCH 10/13] Bump version to 1.0.9:3-beta.2 Co-Authored-By: Claude Opus 4.6 (1M context) --- startos/install/versions/index.ts | 2 +- startos/install/versions/{v1.0.9.3.b1.ts => v1.0.9.3.b2.ts} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename startos/install/versions/{v1.0.9.3.b1.ts => v1.0.9.3.b2.ts} (84%) diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 570f09a..78f00e3 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,2 +1,2 @@ -export { v_1_0_9_3_b1 as current } from './v1.0.9.3.b1' +export { v_1_0_9_3_b2 as current } from './v1.0.9.3.b2' export const other = [] diff --git a/startos/install/versions/v1.0.9.3.b1.ts b/startos/install/versions/v1.0.9.3.b2.ts similarity index 84% rename from startos/install/versions/v1.0.9.3.b1.ts rename to startos/install/versions/v1.0.9.3.b2.ts index 268c587..d2655b8 100644 --- a/startos/install/versions/v1.0.9.3.b1.ts +++ b/startos/install/versions/v1.0.9.3.b2.ts @@ -1,7 +1,7 @@ import { VersionInfo } from '@start9labs/start-sdk' -export const v_1_0_9_3_b1 = VersionInfo.of({ - version: '1.0.9:3-beta.1', +export const v_1_0_9_3_b2 = VersionInfo.of({ + version: '1.0.9:3-beta.2', releaseNotes: { en_US: 'Update to StartOS SDK beta.60', es_ES: 'Actualización a StartOS SDK beta.60', From 94a6aff89993c7fff15a4bcb93a721b31c23d377 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 23 Mar 2026 13:29:27 -0600 Subject: [PATCH 11/13] Bump SDK to beta.65, version bump, fix README Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 7 ++-- package-lock.json | 36 +++++++++---------- package.json | 2 +- startos/install/versions/index.ts | 2 +- .../{v1.0.9.3.b2.ts => v1.0.9.3.b3.ts} | 4 +-- 5 files changed, 25 insertions(+), 26 deletions(-) rename startos/install/versions/{v1.0.9.3.b2.ts => v1.0.9.3.b3.ts} (84%) diff --git a/README.md b/README.md index 5cce77d..61f8fed 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ > **Upstream docs:** > > Everything not listed in this document should behave the same as upstream -> MySpeed v1.0.9. If a feature, setting, or behavior is not mentioned +> MySpeed. If a feature, setting, or behavior is not mentioned > here, the upstream documentation is accurate and fully applicable. MySpeed is a speed test analysis software that records your internet speed for up to 30 days. It automates speed tests using Cron expressions and generates clear statistics on speed, ping, and more. See the [upstream repo](https://github.com/gnmyt/myspeed) for general MySpeed documentation. @@ -36,7 +36,7 @@ MySpeed is a speed test analysis software that records your internet speed for u | Property | Value | |----------|-------| -| Image | Custom Dockerfile extending `germannewsmaker/myspeed:1.0.9` | +| Image | Custom Dockerfile extending `germannewsmaker/myspeed` | | Architectures | x86_64, aarch64 | | Entrypoint | `tini -- node server` | @@ -130,8 +130,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for build instructions and development wo ```yaml package_id: my-speed -upstream_version: "1.0.9" -image: custom Dockerfile (germannewsmaker/myspeed:1.0.9 + tini) +image: custom Dockerfile (germannewsmaker/myspeed + tini) architectures: [x86_64, aarch64] volumes: main: /myspeed/data diff --git a/package-lock.json b/package-lock.json index 464f505..4f0e710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.62" + "@start9labs/start-sdk": "^0.4.0-beta.65" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.62", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.62.tgz", - "integrity": "sha512-D1e30kr7Faojxdakwi5FZf3vSVhjaPHXwFG5a6WY2t5cJL1TG+Gw8p9RrCj9Ttvgc63yxP/CrF1xPtEGFm9vSA==", + "version": "0.4.0-beta.65", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.65.tgz", + "integrity": "sha512-cnphP49d7ZNY3z1wdtnMD/Zr3yX26er2tJQuCRJ3xmIkfrhxoPo/wZgOfkmZRF4lNtPQmHcoMGqtgyz1TLrCew==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", @@ -119,9 +119,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.5.7", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.7.tgz", - "integrity": "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg==", + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", + "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", "funding": [ { "type": "github", @@ -131,8 +131,8 @@ "license": "MIT", "dependencies": { "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.1.3", - "strnum": "^2.2.0" + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -202,9 +202,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", "funding": [ { "type": "github", @@ -233,9 +233,9 @@ } }, "node_modules/strnum": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", - "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", "funding": [ { "type": "github", @@ -294,9 +294,9 @@ } }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 061d424..32744fd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.62" + "@start9labs/start-sdk": "^0.4.0-beta.65" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts index 78f00e3..658a00b 100644 --- a/startos/install/versions/index.ts +++ b/startos/install/versions/index.ts @@ -1,2 +1,2 @@ -export { v_1_0_9_3_b2 as current } from './v1.0.9.3.b2' +export { v_1_0_9_3_b3 as current } from './v1.0.9.3.b3' export const other = [] diff --git a/startos/install/versions/v1.0.9.3.b2.ts b/startos/install/versions/v1.0.9.3.b3.ts similarity index 84% rename from startos/install/versions/v1.0.9.3.b2.ts rename to startos/install/versions/v1.0.9.3.b3.ts index d2655b8..c1ee030 100644 --- a/startos/install/versions/v1.0.9.3.b2.ts +++ b/startos/install/versions/v1.0.9.3.b3.ts @@ -1,7 +1,7 @@ import { VersionInfo } from '@start9labs/start-sdk' -export const v_1_0_9_3_b2 = VersionInfo.of({ - version: '1.0.9:3-beta.2', +export const v_1_0_9_3_b3 = VersionInfo.of({ + version: '1.0.9:3-beta.3', releaseNotes: { en_US: 'Update to StartOS SDK beta.60', es_ES: 'Actualización a StartOS SDK beta.60', From 99c29dfd1485d931df143c0f80a26446d67d2a8f Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 24 Mar 2026 17:49:58 -0600 Subject: [PATCH 12/13] sdk beta.66 + version bump --- package-lock.json | 8 ++++---- package.json | 2 +- startos/index.ts | 2 +- startos/init/index.ts | 2 +- startos/install/versions/index.ts | 2 -- startos/{install/versionGraph.ts => versions/index.ts} | 6 +++--- .../versions/v1.0.9.3.b3.ts => versions/v1.0.9.3.b4.ts} | 4 ++-- 7 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 startos/install/versions/index.ts rename startos/{install/versionGraph.ts => versions/index.ts} (55%) rename startos/{install/versions/v1.0.9.3.b3.ts => versions/v1.0.9.3.b4.ts} (84%) diff --git a/package-lock.json b/package-lock.json index 4f0e710..83d40f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "myspeed-startos", "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.65" + "@start9labs/start-sdk": "^0.4.0-beta.66" }, "devDependencies": { "@types/node": "^22.19.0", @@ -49,9 +49,9 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "0.4.0-beta.65", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.65.tgz", - "integrity": "sha512-cnphP49d7ZNY3z1wdtnMD/Zr3yX26er2tJQuCRJ3xmIkfrhxoPo/wZgOfkmZRF4lNtPQmHcoMGqtgyz1TLrCew==", + "version": "0.4.0-beta.66", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-0.4.0-beta.66.tgz", + "integrity": "sha512-fsp7d3nqFapLohaKbNk1frW8fbVmjr2CBehVXVUk6JS/LSerogqlAYUFE5LKp5vJoTkuSnZbF5np4m+oxCmwCw==", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/package.json b/package.json index 32744fd..5b13aa2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.65" + "@start9labs/start-sdk": "^0.4.0-beta.66" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/index.ts b/startos/index.ts index 5bc2685..7af589b 100644 --- a/startos/index.ts +++ b/startos/index.ts @@ -7,5 +7,5 @@ export { init, uninit } from './init' export { actions } from './actions' import { buildManifest } from '@start9labs/start-sdk' import { manifest as sdkManifest } from './manifest' -import { versionGraph } from './install/versionGraph' +import { versionGraph } from './versions' export const manifest = buildManifest(versionGraph, sdkManifest) diff --git a/startos/init/index.ts b/startos/init/index.ts index f69244d..2e671e5 100644 --- a/startos/init/index.ts +++ b/startos/init/index.ts @@ -1,7 +1,7 @@ import { sdk } from '../sdk' import { setDependencies } from '../dependencies' import { setInterfaces } from '../interfaces' -import { versionGraph } from '../install/versionGraph' +import { versionGraph } from '../versions' import { actions } from '../actions' import { restoreInit } from '../backups' diff --git a/startos/install/versions/index.ts b/startos/install/versions/index.ts deleted file mode 100644 index 658a00b..0000000 --- a/startos/install/versions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { v_1_0_9_3_b3 as current } from './v1.0.9.3.b3' -export const other = [] diff --git a/startos/install/versionGraph.ts b/startos/versions/index.ts similarity index 55% rename from startos/install/versionGraph.ts rename to startos/versions/index.ts index 5a4fad7..59264a2 100644 --- a/startos/install/versionGraph.ts +++ b/startos/versions/index.ts @@ -1,7 +1,7 @@ import { VersionGraph } from '@start9labs/start-sdk' -import { current, other } from './versions' +import { v_1_0_9_3_b4 } from './v1.0.9.3.b4' export const versionGraph = VersionGraph.of({ - current, - other, + current: v_1_0_9_3_b4, + other: [], }) diff --git a/startos/install/versions/v1.0.9.3.b3.ts b/startos/versions/v1.0.9.3.b4.ts similarity index 84% rename from startos/install/versions/v1.0.9.3.b3.ts rename to startos/versions/v1.0.9.3.b4.ts index c1ee030..527e4f2 100644 --- a/startos/install/versions/v1.0.9.3.b3.ts +++ b/startos/versions/v1.0.9.3.b4.ts @@ -1,7 +1,7 @@ import { VersionInfo } from '@start9labs/start-sdk' -export const v_1_0_9_3_b3 = VersionInfo.of({ - version: '1.0.9:3-beta.3', +export const v_1_0_9_3_b4 = VersionInfo.of({ + version: '1.0.9:3-beta.4', releaseNotes: { en_US: 'Update to StartOS SDK beta.60', es_ES: 'Actualización a StartOS SDK beta.60', From e03504aef6389c869868898fd70a7881d6479a07 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 31 Mar 2026 18:15:52 -0600 Subject: [PATCH 13/13] =?UTF-8?q?Release=201.0.9:3=20=E2=80=94=20SDK=201.0?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 2 +- startos/manifest/index.ts | 2 +- startos/versions/index.ts | 4 ++-- startos/versions/{v1.0.9.3.b4.ts => v1.0.9.3.ts} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename startos/versions/{v1.0.9.3.b4.ts => v1.0.9.3.ts} (84%) diff --git a/package.json b/package.json index 5b13aa2..9eea049 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check": "tsc --noEmit" }, "dependencies": { - "@start9labs/start-sdk": "^0.4.0-beta.66" + "@start9labs/start-sdk": "1.0.0" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 17524a7..927fcb1 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -5,7 +5,7 @@ export const manifest = setupManifest({ id: 'my-speed', title: 'MySpeed', license: 'MIT', - packageRepo: 'https://github.com/Start9Labs/myspeed-startos/tree/update/040', + packageRepo: 'https://github.com/Start9Labs/myspeed-startos', upstreamRepo: 'https://github.com/gnmyt/myspeed', marketingUrl: 'https://myspeed.dev/', donationUrl: 'https://ko-fi.com/gnmyt', diff --git a/startos/versions/index.ts b/startos/versions/index.ts index 59264a2..6e5e9af 100644 --- a/startos/versions/index.ts +++ b/startos/versions/index.ts @@ -1,7 +1,7 @@ import { VersionGraph } from '@start9labs/start-sdk' -import { v_1_0_9_3_b4 } from './v1.0.9.3.b4' +import { v_1_0_9_3 } from './v1.0.9.3' export const versionGraph = VersionGraph.of({ - current: v_1_0_9_3_b4, + current: v_1_0_9_3, other: [], }) diff --git a/startos/versions/v1.0.9.3.b4.ts b/startos/versions/v1.0.9.3.ts similarity index 84% rename from startos/versions/v1.0.9.3.b4.ts rename to startos/versions/v1.0.9.3.ts index 527e4f2..cfdd6c0 100644 --- a/startos/versions/v1.0.9.3.b4.ts +++ b/startos/versions/v1.0.9.3.ts @@ -1,7 +1,7 @@ import { VersionInfo } from '@start9labs/start-sdk' -export const v_1_0_9_3_b4 = VersionInfo.of({ - version: '1.0.9:3-beta.4', +export const v_1_0_9_3 = VersionInfo.of({ + version: '1.0.9:3', releaseNotes: { en_US: 'Update to StartOS SDK beta.60', es_ES: 'Actualización a StartOS SDK beta.60',