diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 7c68b079f..000000000 --- a/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# https://editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.env.example b/.env.example deleted file mode 100644 index ea0488ffe..000000000 --- a/.env.example +++ /dev/null @@ -1,53 +0,0 @@ -# ----------------------------------------------------------------------------- -# Server Configuration (dev defaults, required in all environments) -# ----------------------------------------------------------------------------- - -# Port the registry server listens on -# - Dev/Test: 9000 (from this file) -# - Prod: Set in deployment config -SERVER_PORT=9000 - -# SQLite database path (relative to working directory) -# - Dev: Uses local ./db directory -# - Test: Overridden to use temp state directory -# - Prod: Set to production database path -DATABASE_URL="sqlite:db/registry.sqlite3" - -# ----------------------------------------------------------------------------- -# Secrets (required for production, use dummy values for local dev) -# ----------------------------------------------------------------------------- -# IMPORTANT: Never commit real secrets. The values below are dummies for testing. - -# GitHub personal access token for pacchettibotti bot -# Used for: commits to registry repos, issue management -PACCHETTIBOTTI_TOKEN="ghp_pacchettibotti_token" - -# Pacchettibotti SSH keys (base64-encoded) -# Used for: signing authenticated operations (unpublish, transfer) -# Generate with: ssh-keygen -t ed25519 -C "pacchettibotti@purescript.org" -# Encode with: cat key | base64 | tr -d '\n' -PACCHETTIBOTTI_ED25519_PUB="c3NoLWVkMjU1MTkgYWJjeHl6IHBhY2NoZXR0aWJvdHRpQHB1cmVzY3JpcHQub3Jn" -PACCHETTIBOTTI_ED25519="YWJjeHl6" - -# DigitalOcean Spaces credentials for S3-compatible storage -# Used for: uploading/downloading package tarballs -SPACES_KEY="digitalocean_spaces_key" -SPACES_SECRET="digitalocean_spaces_secret" - - -# ----------------------------------------------------------------------------- -# Debug / Development Options -# ----------------------------------------------------------------------------- - -# When "true", the server skips all writes: git push, S3 upload, Pursuit publish. -# Reads and compilations still run normally. Useful for reproducing slow jobs -# locally without affecting the real registry. -# READONLY=true - -# ----------------------------------------------------------------------------- -# Script-only Secrets (not used by server, used by scripts like legacy-importer) -# ----------------------------------------------------------------------------- - -# Personal GitHub token for API requests when running scripts -# This is YOUR token, not pacchettibotti's -GITHUB_TOKEN="ghp_your_personal_access_token" diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30f2..000000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index 7d08d2dd1..000000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,2 +0,0 @@ -# nixfmt v0.6.0 -c428bcf158275637597b37d683f82248833664c0 diff --git a/.github/workflows/dashboard.yml b/.github/workflows/dashboard.yml deleted file mode 100644 index 11987dd1c..000000000 --- a/.github/workflows/dashboard.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Deploy Dashboard - -on: - push: - branches: [master] - paths: ['dashboard/**'] - pull_request: - types: [opened, reopened, synchronize, closed] - paths: ['dashboard/**'] - -permissions: - contents: write - pull-requests: write - -concurrency: pages-${{ github.ref }} - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node - if: github.event.action != 'closed' - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Install tools - if: github.event.action != 'closed' - run: npm install --no-save purescript@0.15.16 spago@1.0.4 esbuild - - - name: Build dashboard - if: github.event.action != 'closed' - run: npm run dashboard:build - - - name: Verify bundle - if: github.event.action != 'closed' - run: test -f dashboard/app.js - - - name: Prepare deploy directory - if: github.event.action != 'closed' - run: | - mkdir -p _site - cp dashboard/index.html _site/ - cp dashboard/app.js _site/ - cp -r dashboard/static _site/ - - - name: Deploy to Pages - if: github.ref == 'refs/heads/master' - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: _site - clean-exclude: pr-preview - - # On 'closed' events this removes the preview directory from gh-pages; - # on all other PR events it deploys the build to pr-preview/pr-/. - - name: Deploy PR preview - if: github.event_name == 'pull_request' - uses: rossjrw/pr-preview-action@v1 - with: - source-dir: ./_site/ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 1fd95d772..000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: deploy - -on: - push: - branches: [master] - -jobs: - deploy: - # Deploys should only occur from the master branch - if: github.ref_name == 'master' - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup SSH keys and known_hosts - env: - SSH_AUTH_SOCK: /tmp/ssh_agent.sock - run: | - ssh-agent -a $SSH_AUTH_SOCK > /dev/null - ssh-add - <<< "${{ secrets.PACCHETTIBOTTI_SSH_KEY }}" - - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v4 - - - name: Setup Nix cache - uses: DeterminateSystems/magic-nix-cache-action@v2 - - - name: Deploy with Colmena - env: - SSH_AUTH_SOCK: /tmp/ssh_agent.sock - run: | - nix develop --command colmena apply diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 77f60a901..000000000 --- a/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -/node_modules -/output -/output-es -/scratch -/.vscode -/scripts/analysis -/generated-docs - -result* - -.psc* -.purs* -.psa* -.spago -.cache -.direnv -*.sqlite3 -*.sqlite3-wal -*.sqlite3-shm - -TODO.md -.spec-results -/generated-docs - -# Generated bundle -dashboard/app.js - -# Keep it secret, keep it safe. -.env diff --git a/.tidyrc.json b/.tidyrc.json deleted file mode 100644 index 9e39cf11d..000000000 --- a/.tidyrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "importSort": "ide", - "importWrap": "source", - "indent": 2, - "operatorsFile": null, - "ribbon": 1, - "typeArrowPlacement": "first", - "unicode": "never", - "width": null -} diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 4347a244a..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,147 +0,0 @@ -# AGENTS.md - -The PureScript Registry implements a package registry for PureScript. - -- @SPEC.md contains the registry specification. Use this to understand the core data types and operations of the registry. -- @CONTRIBUTING.md describes the structure of the registry codebase, what the various packages in the monorepo represent, and how to build and test the registry code. - -## Development Environment - -We use Nix with direnv. Expect to be in a Nix shell automatically, but if you are not, you can enter one: - -```sh -nix develop -``` - -### Nix Quirks - -- If Nix tries to fetch from git during a build and fails, then most likely `spago.yaml` files have been changed but the lockfiles were not updated. Update them with `spago build`. -- If a Nix build appears to be stale, then most likely files were modified but not tracked by Git. Nix flakes only consider Git-tracked files. Add modified files with `git add` and retry. - -### Build & Test Commands - -The registry is implemented in PureScript. Use spago to build it. - -```sh -spago build # Build all PureScript code -``` - -The registry contains unit tests, end-to-end tests, and nix flake checks. - -- Run unit tests when you complete a change with `spago test` or `spago test -p `. -- Run end-to-end tests when working on the registry server in `app`. -- On Linux systems you can run all flake checks (the tests run in CI) using `nix flake check -L`. - -### End-to-End Tests - -The end-to-end (integration) tests are in `app-e2e`. They can be run via Nix on Linux: - -```sh -nix build .#checks.x86_64-linux.integration -``` - -Alternately, they can be run on macOS or for more iterative development of tests using two terminals: one to start the test env, and one to execute the tests. - -```sh -# Terminal 1: Start test environment (wiremock mocks + registry server on port 9000) -nix run .#test-env - -# Terminal 2: Run E2E tests once server is ready -spago-test-e2e -``` - -Options: `nix run .#test-env -- --tui` for interactive TUI, `-- --detached` for background mode to use a single terminal. - -State is stored in `/tmp/registry-test-env` and cleaned up on each `nix run .#test-env`. To examine state after a test run (for debugging), stop the test-env but don't restart it. This is useful, for example, to read the logs of the most recent run. For example: - -```sh -# after a test run, see the logs (log name is today's date) -cat /tmp/registry-test-env/scratch/logs/*.log -``` - -#### Smoke Test (Linux only) - -The smoke test verifies that the server comes up properly and tests deployment. Only run this test if you are making changes which could break the deployment of the server. - -```sh -nix build .#checks.x86_64-linux.smoke -L -``` - -## Formatting - -```sh -# Format PureScript -purs-tidy format-in-place app app-e2e foreign lib scripts -purs-tidy check app app-e2e foreign lib scripts - -# Format Nix files -nixfmt *.nix nix/**/*.nix -``` - -## Project Structure - -- `app/` — Registry server implementation. -- `app-e2e/` — E2E tests for the server API. -- `lib/` — **Public library** for consumers (Spago, Pursuit, etc.). Only types and functions useful to external tools belong here. Avoid implementation-specific code. -- `foreign/` — FFI bindings to JavaScript libraries. -- `scripts/` — Runnable modules for registry tasks (PackageTransferrer, PackageSetUpdater, DailyImporter, etc.). Run via `nix run .#package-transferrer`, etc. -- `test-utils/` — Shared test utilities. -- `db/` — SQLite schemas and migrations (use `dbmate up` to initialize). -- `types/` — Dhall type specifications. -- `nix/` — Nix build and deployment configuration. - -## Scratch Directory & Caching - -The `scratch/` directory (gitignored) is used by scripts for: -- `.cache/` — Cached API responses, downloaded packages, etc. -- `logs/` — Log files -- `registry/`, `registry-index/` — Local clones for testing, also modified and optionally committed to by scripts - -Caching is critical for the legacy importer due to the expense of downloading packages. The `Registry.App.Effect.Cache` module handles caching. - -## PureScript Conventions - -### Custom Prelude - -Always use `Registry.App.Prelude` in `app/` and `app-e2e/` directories: - -```purescript -import Registry.App.Prelude -``` - -### Effects via Run - -Use the `run` library for extensible effects. Do NOT perform HTTP calls, console logs, or other effects directly in `Aff`. Check for existing effects in `app/src/App/Effect/` or consider adding one. - -### Import Style - -Import types unqualified, values qualified. Use shortened module names: - -```purescript -import Registry.App.Prelude - -import Data.Array as Array -import Data.String as String -import Node.FS.Aff as FS.Aff -import Parsing (Parser) -import Parsing as Parsing -import Parsing.Combinators as Parsing.Combinators -import Registry.Operation (AuthenticatedData) -import Registry.SSH as SSH -``` - -### Syntax - -Never use `let/in` syntax unless in an `ado` block. Always `do/let`. - -```purs -func = - -- NEVER use let/in syntax - let x = 1 - in x + x - -func = do - -- ALWAYS use do/let syntax - let x = 1 - x + x -``` diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 120000 index 47dc3e3d8..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index ef018a3fd..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,301 +0,0 @@ -# Contributing to the PureScript Registry - -Welcome to the PureScript Registry development repository! This file helps you get up to speed contributing to the registry. - -## Getting Started - -You can get all the tools you need to work on this repository via Nix by entering the Nix shell: - -```sh -nix develop -``` - -Then, you can treat this repository as an ordinary PureScript project: - -```sh -# Build the source -spago build - -# Run the tests -spago test -``` - -> NOTE: You don't strictly need Nix to work on the registry; if you are contributing PureScript changes only then you can get away with just `purs` and `spago`, but be aware that some tests will fail on your machine, you may see database errors, and you will not be able to run integration tests. - -## Repository Structure - -The registry is a significant PureScript application split into several runnable modules. - -- `app` is the main application and contains the registry server and the GitHub-based API. App code goes here. -- `foreign` contains library code for FFI bindings to JavaScript libraries. Any FFI you write should go here. -- `lib` contains library code meant for other PureScript packages (such as Spago) to reuse. Core registry types and functions go here, and we are careful not to introduce breaking changes unless absolutely necessary. -- `scripts` contains runnable modules written on top of the app for performing registry tasks like uploading and transferring packages. - -There are two additional PureScript directories focused on testing only: - -- `app-e2e` contains tests that exercise the server API, which requires that a server and associated wiremock services are running -- `test-utils` contains utility code intended only for tests - -There is one additional directory for the web dashboard: - -- `dashboard` contains the static HTML/CSS/JS dashboard for monitoring registry jobs. It is deployed independently to GitHub Pages and requires no build step. - -There are three more directories containing code for the registry. - -- `db` contains schemas and migrations for the sqlite3 database used by the server. -- `nix` contains Nix code for building and deploying the registry server to Digital Ocean. -- `types` contains Dhall specifications for the core registry types - -Finally, the `flake.nix` file orchestrates builds for the whole repository. - -## Running the Registry Server Locally - -The registry server requires a `.env` file and an initialized database. To run the server for development: - -```sh -# 1. Ensure database is initialized (only needed once) -dbmate up - -# 2. Run the server (from the nix shell) -cd app && spago run -``` - -The server will load environment variables from the `.env` file in the project root and run on port 8080 by default. - -## Quick Start: Running Integration Tests - -There are two kinds of tests we use to verify the registry server. The first is a smoke test which runs in a Nix VM and is used to verify deployment only. The second is a lightweight combination of the server with wiremock instances to mock external services. Here's how to run them: - -### Smoke Test - -You can run the smoke test with the following -- Linux-only. - -```sh -nix build .#checks.x86_64-linux.smoke -L -``` - -### Integration Test - -You can run the integration tests with the following on Linux: - -```sh -nix build .#checks.x86_64-linux.integration -L -``` - -On macOS or for iterative development, you can instead start the test environment and run the tests separately. - -```sh -# Terminal 1: Start the test environment (wiremock mocks + registry server) -nix run .#test-env - -# Terminal 2: Run E2E tests once server is ready -spago-test-e2e -``` - -The test environment: -- Starts wiremock services mocking GitHub, S3, Pursuit, etc. -- Starts the registry server with a temporary SQLite database -- Uses fixture data from `app/fixtures/` -- State is stored in `/tmp/registry-test-env` and cleaned up on each `nix run .#test-env` - -Press `Ctrl+C` in Terminal 1 to stop all services. - -All arguments after `--` are passed directly to process-compose: - -```sh -# Use interactive TUI instead of log streaming -nix run .#test-env -- --tui - -# Run detached (background mode) -nix run .#test-env -- --detached - -# When detached, manage with process-compose: -process-compose attach # Attach TUI -process-compose down # Stop all services -``` - -To examine state after a test run (e.g., for debugging), stop the test-env but don't restart it. The state remains in `/tmp/registry-test-env`: -- `db/registry.sqlite3` — SQLite database -- `scratch/registry/` — Local registry clone with metadata -- `scratch/registry-index/` — Local manifest index clone -- `repo-fixtures/` — Git fixture repositories - -## Available Nix Commands - -You can also run the packaged registry server: - -```sh -nix run .#server -``` - -You can also run any of the modules listed in the [scripts](./scripts/) directory by converting the camel-case file name to kebab-case, such as: - -```sh -# To run `PackageTransferrer.purs` -nix run .#package-transferrer - -# To run `DailyImporter.purs` -nix run .#daily-importer -``` - -### Required Environment Variables - -The [.env.example](./.env.example) file lists out a number of environment variables that you can set. Scripts that require environment variables will fail at startup if the required env var is not found, so you can add only the ones you need to your .env file. - -## Upgrading the PureScript Compiler - -When a new compiler release is added to `purescript-overlay`, the registry needs a small set of changes: - -```sh -# Update the Nix input that provides purs and the compiler binaries -nix flake update purescript-overlay - -# Confirm the dev shell now exposes the expected compiler version -nix develop --command purs --version -``` - -If the compiler bump causes source incompatibilities, fix them in the PureScript code and re-run the commands above. Then, update the expected compiler list in [`app/test/App/CLI/PursVersions.purs`](./app/test/App/CLI/PursVersions.purs). Once done, verify with `nix flake check`. - -Deploying the updated server is normally enough to start the registry-wide compiler matrix rollout. On server startup, the job executor checks whether the newest compiler known to `purs-versions` is missing from the latest `prelude` metadata; if so, it enqueues matrix jobs for packages without dependencies, and those jobs cascade through the rest of the registry. - -In practice this means: - -- Push the compiler upgrade to `master` and let the normal deploy workflow roll it out, or deploy manually with `colmena apply`. -- After deploy, watch `journalctl -u server.service` or the jobs API if you want to confirm the new compiler matrix has started. - -## Testing - -The usual PureScript testing workflow applies in the registry — from within a Nix shell, you can execute all tests: - -```sh -spago test -``` - -There are also a number of checks run by the Nix flake for non-PureScript code, such as verifying Dhall types. Run them: - -```sh -nix flake check -L -``` - -### Testing Guidelines - -The PureScript code in the registry is well-tested, ranging from tests for individual functions to full end-to-end tests for the registry server running in a NixOS machine configured the same way as the deployed machine. The smaller and more pure the test, the easier it is to write and maintain; most code is tested via unit tests written with `spec`, and only the core pipelines are run in the integration test. - -Each PureScript workspace has a `test` directory containing tests written with `spec`. For example, see the [`lib`](./lib/test/), [`foreign`](./foreign/test/), or [`app`](./app/test/) test directories. If you write a new function in e.g. the `foreign` workspace, then write tests in the `foreign/test` directory. - -In general we prefer that tests are self-contained in PureScript. For example, if you need to decode some JSON data, prefer to write that data in a PureScript string and decode the string rather than read a JSON file from disk. If a function must read from disk, prefer to generate test data and write it to a `tmp` directory rather than commit test files to the repository. - -However, in the rare case where you do need to store test data in the repository, you can do so in the `fixtures` directory. For example, see the [`lib` fixtures](./lib/fixtures/) or the [`app` fixtures](./app/fixtures/). (These are only for tests, but they're kept outside the `test` directory so that Spago doesn't try to compile PureScript code found in the fixtures when running `spago test`.) - -### Mock Tests - -The registry source code is defined such that most effectful code is abstracted by the [`App.Effect`](./app/src/App/Effect) modules. This allows us to mock those effects for testing purposes. For example, the `publish` function will download packages from S3 using the `STORAGE` effect, publish documentation to Pursuit with the `PURSUIT` effect, write to the registry repository with the `REGISTRY` effect, fetch data from remote repositories with the `GITHUB` effect, and more. - -We obviously don't want to perform these effects in our tests, but we still want to test that the `publish` function behaves as expected. If you are writing a test for a function written in `Run (SOME_EFFECT + r) a`, like `publish`, then you will need to mock effects when writing your tests. You can see the mock implementations in the [`Registry.Test.Assert.Run`](./app/test/Test/Assert/Run.purs) module. - -The mock tests use fixtures to represent remote resources. For example, instead of a remote S3 bucket we have the [`registry-storage`](./app/fixtures/registry-storage/) fixtures; this directory is our 'storage backend' and we can 'download' tarballs from it and 'upload' tarballs to it. Instead of accessing arbitrary GitHub repositories we have the [`github-packages`](./app/fixtures/github-packages/) fixtures. Instead of the upstream registry, registry-index, and package-sets repositories, we have e.g. the [`registry-index`](./app/fixtures/registry-index) fixtures. - -The function under test will only have access to data in these fixtures. - -### Notes on Mocking External Services - -The registry relies heavily on external services like git forges (via `git`), as well as the Pursuit, GitHub, S3, and other APIs. We mock these services so we can test the server locally without modifying any sensitive data. Below is our approach. - -#### Intercepting Git - -Git can use a local file path instead of a remote URL, such that `git clone my-path new-repo` clones to `new-repo` and sets as its origin `my-path`. You can even make changes and push to the upstream if the upstream has been configured with `receive.denyCurrentBranch` set to `ignore`, though this wrecks the upstream's working index. For the sake of tests this doesn't matter. - -To support the integration test we supply a wrapped version of `git` that replaces URLs of the form `https://.../...` with `file://.../...`, where `` is a temporary directory set up with fake repositories built from the fixtures at runtime. For example, this path for the registry-index repository might be `file:///tmp/repo-fixtures/purescript/registry-index`. In this way we can replace various possible Git servers the registry may contact with local fixtures instead. - -The wrapped git needs to know where the fixture data lives on the integration test virtual machine, and so we thread a `REPO_FIXTURES_DIR` environment variable through the systemd service for the server to the wrapper script. Packages will be cloned from that directory instead of from GitHub. - -#### Intercepting HTTPS - -Likewise, we can replace HTTP requests with [wiremock](https://wiremock.org). This tool allows us to return fixture results to HTTP requests. Each API we access has its own wiremock service set up with fixture data. The wiremock configuration (ports, mappings, and fixture files) is centralized in [`nix/test/config.nix`](./nix/test/config.nix). Environment variables are defined in [`nix/lib/env.nix`](./nix/lib/env.nix). Instead of sending requests to e.g. the GitHub API at https://api.github.com we send them to the local Wiremock server. To do that, we configure our integration test with the base URLs for each API we hit. For example: - -```sh -# Requests to the GitHub API via Octokit -GITHUB_API_URL=http://localhost:9001 - -# Requests to packages.registry.purescript.org, e.g. downloads -S3_API_URL=https://localhost:9002 - -# Requests to the underlying S3 bucket, e.g. 'listObjects' -S3_BUCKET_URL=https://localhost:9003 - -# Requests to pursuit.purescript.org -PURSUIT_API_URL=https://localhost:9004 -``` - -For each service definition we include request/response pairs we intend to be available on our local API, written in Nix. Here's a short example of creating a mock GitHub API with a request/response pair; in the deployed virtual machine, requests to the GitHub API can be made to http://localhost:9001. - -```nix -services.wiremock-github-api = { - enable = true; - port = 9001; - mappings = [ - { - request = { - method = "GET"; - url = "/repos/purescript/package-sets/tags"; - }; - response = { - status = 200; - headers."Content-Type" = "application/json"; - jsonBody = { - name = "psc-0.15.10-20230105"; - commit = { - sha = "090897c992b2b310b1456506308db789672adac1"; - url = "https://api.github.com/repos/purescript/package-sets/commits/090897c992b2b310b1456506308db789672adac1"; - }; - }; - }; - } - ]; -}; -``` - -It is also possible to include specific files that should be returned to requests via the `files` key. Here's another short example of setting up an S3 mock, in which we copy files from the fixtures into the wiremock service's working directory given a particular file name, and then write request/response mappings that respond to requests by reading the file at path given by `bodyFileName`. - -## Dashboard Development - -The `dashboard/` directory contains a Halogen (PureScript) application for monitoring registry jobs. It is deployed to GitHub Pages and calls the registry API cross-origin. - -### Building - -To produce a browser JS bundle: - -```sh -npm run dashboard:build -``` - -This outputs `dashboard/app.js`, which `dashboard/index.html` loads via ` + + diff --git a/dashboard/static/style.css b/pr-preview/pr-761/static/style.css similarity index 100% rename from dashboard/static/style.css rename to pr-preview/pr-761/static/style.css diff --git a/scripts/spago.yaml b/scripts/spago.yaml deleted file mode 100644 index a3bdb19f1..000000000 --- a/scripts/spago.yaml +++ /dev/null @@ -1,27 +0,0 @@ -package: - name: registry-scripts - publish: - license: BSD-3-Clause - version: 0.0.1 - dependencies: - - aff - - argparse-basic - - arrays - - codec-json - - console - - datetime - - either - - fetch - - foldable-traversable - - formatters - - json - - node-fs - - node-path - - node-process - - ordered-collections - - prelude - - registry-app - - registry-foreign - - registry-lib - - run - - strings diff --git a/scripts/src/DailyImporter.purs b/scripts/src/DailyImporter.purs deleted file mode 100644 index 0e65cdecf..000000000 --- a/scripts/src/DailyImporter.purs +++ /dev/null @@ -1,229 +0,0 @@ --- | This script checks for new package versions by fetching commits from the last --- | 24 hours for all packages in the registry. When a recent commit is found that --- | corresponds to an unpublished version tag, it submits a publish job to the --- | registry API. --- | --- | Run via Nix: --- | nix run .#daily-importer -- --dry-run # Log what would be submitted --- | nix run .#daily-importer -- --submit # Actually submit to the API --- | --- | Required environment variables: --- | GITHUB_TOKEN - GitHub API token for fetching commits and tags --- | REGISTRY_API_URL - Registry API URL (default: https://registry.purescript.org) -module Registry.Scripts.DailyImporter where - -import Registry.App.Prelude - -import ArgParse.Basic (ArgParser) -import ArgParse.Basic as Arg -import Codec.JSON.DecodeError as CJ.DecodeError -import Data.Array as Array -import Data.Codec.JSON as CJ -import Data.DateTime as DateTime -import Data.Map as Map -import Data.Set as Set -import Data.String as String -import Data.Time.Duration (Hours(..)) -import Effect.Aff as Aff -import Effect.Class.Console as Console -import Fetch (Method(..)) -import Fetch as Fetch -import JSON as JSON -import Node.Path as Path -import Node.Process as Process -import Registry.API.V1 as V1 -import Registry.App.CLI.Git as Git -import Registry.App.Effect.Cache as Cache -import Registry.App.Effect.Env (RESOURCE_ENV) -import Registry.App.Effect.Env as Env -import Registry.App.Effect.GitHub (GITHUB) -import Registry.App.Effect.GitHub as GitHub -import Registry.App.Effect.Log (LOG) -import Registry.App.Effect.Log as Log -import Registry.App.Effect.Registry (REGISTRY) -import Registry.App.Effect.Registry as Registry -import Registry.App.Legacy.LenientVersion as LenientVersion -import Registry.Foreign.Octokit as Octokit -import Registry.Location (Location(..)) -import Registry.Operation as Operation -import Registry.PackageName as PackageName -import Run (AFF, EFFECT, Run) -import Run as Run -import Run.Except (EXCEPT) -import Run.Except as Except - -data Mode = DryRun | Submit - -derive instance Eq Mode - -parser :: ArgParser Mode -parser = Arg.choose "command" - [ Arg.flag [ "dry-run" ] - "Log what would be submitted without actually calling the API." - $> DryRun - , Arg.flag [ "submit" ] - "Submit publish jobs to the registry API." - $> Submit - ] - -main :: Effect Unit -main = launchAff_ do - args <- Array.drop 2 <$> liftEffect Process.argv - - let description = "Check for new package versions and submit publish jobs to the registry API." - mode <- case Arg.parseArgs "daily-importer" description parser args of - Left err -> Console.log (Arg.printArgError err) *> liftEffect (Process.exit' 1) - Right command -> pure command - - Env.loadEnvFile ".env" - resourceEnv <- Env.lookupResourceEnv - token <- Env.lookupRequired Env.githubToken - - githubCacheRef <- Cache.newCacheRef - registryCacheRef <- Cache.newCacheRef - let cache = Path.concat [ scratchDir, ".cache" ] - - octokit <- Octokit.newOctokit token resourceEnv.githubApiUrl - debouncer <- Registry.newDebouncer - - let - registryEnv :: Registry.RegistryEnv - registryEnv = - { pull: Git.Autostash - , write: Registry.ReadOnly - , repos: Registry.defaultRepos - , workdir: scratchDir - , debouncer - , cacheRef: registryCacheRef - } - - runDailyImport mode resourceEnv.registryApiUrl - # Except.runExcept - # Registry.interpret (Registry.handle registryEnv) - # GitHub.interpret (GitHub.handle { octokit, cache, ref: githubCacheRef }) - # Log.interpret (Log.handleTerminal Verbose) - # Env.runResourceEnv resourceEnv - # Run.runBaseAff' - >>= case _ of - Left err -> do - Console.error $ "Error: " <> err - liftEffect $ Process.exit' 1 - Right _ -> pure unit - -type DailyImportEffects = (REGISTRY + GITHUB + LOG + RESOURCE_ENV + EXCEPT String + AFF + EFFECT + ()) - -runDailyImport :: Mode -> URL -> Run DailyImportEffects Unit -runDailyImport mode registryApiUrl = do - Log.info "Daily Importer: checking for new package versions..." - - now <- Run.liftEffect nowUTC - let since = fromMaybe now $ DateTime.adjust (Hours (-24.0)) now - - allMetadata <- Registry.readAllMetadata - let packages = Map.toUnfoldable allMetadata :: Array (Tuple PackageName Metadata) - - Log.info $ "Checking " <> show (Array.length packages) <> " packages for commits in the last 24 hours..." - - submitted <- for packages \(Tuple name (Metadata metadata)) -> do - case metadata.location of - Git _ -> pure 0 -- Skip non-GitHub packages for now - GitHub { owner, repo } -> do - let address = { owner, repo } - -- First, check if there are any recent commits - GitHub.listCommitsSince address since >>= case _ of - Left err -> do - Log.debug $ "Failed to fetch commits for " <> PackageName.print name <> ": " <> Octokit.printGitHubError err - pure 0 - Right [] -> do - -- No recent commits, skip fetching tags - Log.debug $ "No recent commits for " <> PackageName.print name - pure 0 - Right recentCommitShas -> do - let recentShas = Set.fromFoldable recentCommitShas - -- There are recent commits, now fetch tags to see if any point to them - GitHub.listTags address >>= case _ of - Left err -> do - Log.debug $ "Failed to fetch tags for " <> PackageName.print name <> ": " <> Octokit.printGitHubError err - pure 0 - Right tags -> do - Log.debug $ "Fetched " <> show (Array.length tags) <> " tags for " <> PackageName.print name <> ": " <> String.joinWith ", " (map _.name tags) - let - publishedVersions = combinedPublishedVersions { published: metadata.published, unpublished: metadata.unpublished } - newVersions = findNewVersions tags recentShas publishedVersions - - -- Submit publish jobs for new versions - count <- for newVersions \{ version, ref } -> do - submitPublishJob mode registryApiUrl name version ref - - pure $ Array.length $ Array.filter identity count - - let totalSubmitted = Array.foldl (+) 0 submitted - Log.info $ "Daily Importer complete. Submitted " <> show totalSubmitted <> " publish jobs." - --- | Combine published and unpublished versions into a set -combinedPublishedVersions :: forall a b. { published :: Map Version a, unpublished :: Map Version b } -> Set Version -combinedPublishedVersions metadata = Set.fromFoldable $ Map.keys metadata.published <> Map.keys metadata.unpublished - --- | Find new version tags that point to recent commits and haven't been published -findNewVersions :: Array Octokit.Tag -> Set String -> Set Version -> Array { version :: Version, ref :: String } -findNewVersions tags recentShas publishedVersions = Array.catMaybes $ tags <#> \tag -> - case LenientVersion.parse tag.name of - Left _ -> Nothing -- Not a valid version tag - Right result -> do - let version = LenientVersion.version result - if not (Set.member tag.sha recentShas) then Nothing -- Tag doesn't point to a recent commit - else if Set.member version publishedVersions then Nothing -- Already published - else Just { version, ref: tag.name } - --- | Submit a publish job for a new package version. The compiler is not specified; the registry --- | API will discover the latest compatible compiler based on the package's dependencies. -submitPublishJob :: Mode -> URL -> PackageName -> Version -> String -> Run DailyImportEffects Boolean -submitPublishJob mode registryApiUrl name version ref = do - let formatted = formatPackageVersion name version - - let - payload :: Operation.PublishData - payload = - { name - , version - , location: Nothing -- Use current metadata location at publish time - , ref - , compiler: Nothing -- Let the API discover the latest compatible compiler - , resolutions: Nothing - } - - case mode of - DryRun -> do - Log.info $ "[DRY RUN] Would submit publish job for " <> formatted - pure true - - Submit -> do - Log.info $ "Submitting publish job for " <> formatted - result <- Run.liftAff $ submitJob (registryApiUrl <> "/v1/publish") payload - case result of - Left err -> do - Log.error $ "Failed to submit publish job for " <> formatted <> ": " <> err - pure false - Right { jobId } -> do - Log.info $ "Submitted publish job " <> unwrap jobId <> " for " <> formatted - pure true - --- | Submit a job to the registry API -submitJob :: String -> Operation.PublishData -> Aff (Either String V1.JobCreatedResponse) -submitJob url payload = do - let body = JSON.print $ CJ.encode Operation.publishCodec payload - result <- Aff.attempt $ Fetch.fetch url - { method: POST - , headers: { "Content-Type": "application/json" } - , body - } - case result of - Left err -> pure $ Left $ "Network error: " <> Aff.message err - Right response -> do - responseBody <- response.text - if response.status >= 200 && response.status < 300 then - case JSON.parse responseBody >>= \json -> lmap CJ.DecodeError.print (CJ.decode V1.jobCreatedResponseCodec json) of - Left err -> pure $ Left $ "Failed to parse response: " <> err - Right r -> pure $ Right r - else - pure $ Left $ "HTTP " <> show response.status <> ": " <> responseBody diff --git a/scripts/src/PackageSetUpdater.purs b/scripts/src/PackageSetUpdater.purs deleted file mode 100644 index 8113a4957..000000000 --- a/scripts/src/PackageSetUpdater.purs +++ /dev/null @@ -1,225 +0,0 @@ --- | This script checks for packages recently uploaded to the registry and --- | submits package set update jobs to add them to the package set. --- | --- | Run via Nix: --- | nix run .#package-set-updater -- --dry-run # Log what would be submitted --- | nix run .#package-set-updater -- --submit # Actually submit to the API --- | --- | Required environment variables: --- | GITHUB_TOKEN - GitHub API token --- | REGISTRY_API_URL - Registry API URL (default: https://registry.purescript.org) -module Registry.Scripts.PackageSetUpdater where - -import Registry.App.Prelude - -import ArgParse.Basic (ArgParser) -import ArgParse.Basic as Arg -import Codec.JSON.DecodeError as CJ.DecodeError -import Data.Array as Array -import Data.Codec.JSON as CJ -import Data.DateTime as DateTime -import Data.Map as Map -import Data.Time.Duration (Hours(..)) -import Effect.Aff as Aff -import Effect.Class.Console as Console -import Fetch (Method(..)) -import Fetch as Fetch -import JSON as JSON -import Node.Path as Path -import Node.Process as Process -import Registry.API.V1 as V1 -import Registry.App.CLI.Git as Git -import Registry.App.Effect.Cache as Cache -import Registry.App.Effect.Env (RESOURCE_ENV) -import Registry.App.Effect.Env as Env -import Registry.App.Effect.GitHub (GITHUB) -import Registry.App.Effect.GitHub as GitHub -import Registry.App.Effect.Log (LOG) -import Registry.App.Effect.Log as Log -import Registry.App.Effect.PackageSets as PackageSets -import Registry.App.Effect.Registry (REGISTRY) -import Registry.App.Effect.Registry as Registry -import Registry.Foreign.Octokit as Octokit -import Registry.Operation (PackageSetOperation(..)) -import Registry.Operation as Operation -import Registry.PackageName as PackageName -import Registry.PackageSet (PackageSet(..)) -import Registry.Version as Version -import Run (AFF, EFFECT, Run) -import Run as Run -import Run.Except (EXCEPT) -import Run.Except as Except - -data Mode = DryRun | Submit - -derive instance Eq Mode - -parser :: ArgParser Mode -parser = Arg.choose "command" - [ Arg.flag [ "dry-run" ] - "Log what would be submitted without actually calling the API." - $> DryRun - , Arg.flag [ "submit" ] - "Submit package set update jobs to the registry API." - $> Submit - ] - -main :: Effect Unit -main = launchAff_ do - args <- Array.drop 2 <$> liftEffect Process.argv - - let description = "Check for recent uploads and submit package set update jobs to the registry API." - mode <- case Arg.parseArgs "package-set-updater" description parser args of - Left err -> Console.log (Arg.printArgError err) *> liftEffect (Process.exit' 1) - Right command -> pure command - - Env.loadEnvFile ".env" - resourceEnv <- Env.lookupResourceEnv - token <- Env.lookupRequired Env.githubToken - - githubCacheRef <- Cache.newCacheRef - registryCacheRef <- Cache.newCacheRef - let cache = Path.concat [ scratchDir, ".cache" ] - - octokit <- Octokit.newOctokit token resourceEnv.githubApiUrl - debouncer <- Registry.newDebouncer - - let - registryEnv :: Registry.RegistryEnv - registryEnv = - { pull: Git.Autostash - , write: Registry.ReadOnly - , repos: Registry.defaultRepos - , workdir: scratchDir - , debouncer - , cacheRef: registryCacheRef - } - - runPackageSetUpdater mode resourceEnv.registryApiUrl - # Except.runExcept - # Registry.interpret (Registry.handle registryEnv) - # GitHub.interpret (GitHub.handle { octokit, cache, ref: githubCacheRef }) - # Log.interpret (Log.handleTerminal Normal) - # Env.runResourceEnv resourceEnv - # Run.runBaseAff' - >>= case _ of - Left err -> do - Console.error $ "Error: " <> err - liftEffect $ Process.exit' 1 - Right _ -> pure unit - -type PackageSetUpdaterEffects = (REGISTRY + GITHUB + LOG + RESOURCE_ENV + EXCEPT String + AFF + EFFECT + ()) - -runPackageSetUpdater :: Mode -> URL -> Run PackageSetUpdaterEffects Unit -runPackageSetUpdater mode registryApiUrl = do - Log.info "Package Set Updater: checking for recent uploads..." - - -- Get the current package set - latestPackageSet <- Registry.readLatestPackageSet >>= case _ of - Nothing -> do - Log.warn "No package set found, skipping package set updates" - pure Nothing - Just set -> pure (Just set) - - for_ latestPackageSet \packageSet -> do - let currentPackages = (un PackageSet packageSet).packages - - -- Find packages uploaded in the last 24 hours - recentUploads <- findRecentUploads (Hours 24.0) - let - -- Filter out packages already in the set at the same or newer version - newOrUpdated = recentUploads # Map.filterWithKey \name version -> - case Map.lookup name currentPackages of - Nothing -> true -- new package - Just currentVersion -> version > currentVersion -- upgrade - - if Map.isEmpty newOrUpdated then - Log.info "No new packages for package set update." - else do - Log.info $ "Found " <> show (Map.size newOrUpdated) <> " candidates to validate" - - -- Pre-validate candidates to filter out packages with missing dependencies - manifestIndex <- Registry.readAllManifests - let candidates = PackageSets.validatePackageSetCandidates manifestIndex packageSet (map Just newOrUpdated) - - unless (Map.isEmpty candidates.rejected) do - Log.info $ "Some packages are not eligible for the package set:\n" <> PackageSets.printRejections candidates.rejected - - -- Only include accepted packages (filter out removals, keep only updates) - let accepted = Map.catMaybes candidates.accepted - - if Map.isEmpty accepted then - Log.info "No packages passed validation for package set update." - else do - Log.info $ "Validated " <> show (Map.size accepted) <> " packages for package set update" - - -- Create a package set update payload - let - payload :: Operation.PackageSetUpdateData - payload = - { compiler: Nothing -- Use current compiler - , packages: map Just accepted -- Just version = add/update - } - - case mode of - DryRun -> do - Log.info $ "[DRY RUN] Would submit package set update with packages:" - for_ (Map.toUnfoldable accepted :: Array _) \(Tuple name version) -> - Log.info $ " - " <> PackageName.print name <> "@" <> Version.print version - - Submit -> do - let - rawPayload = JSON.print $ CJ.encode Operation.packageSetUpdateCodec payload - - request :: Operation.PackageSetUpdateRequest - request = - { payload: PackageSetUpdate payload - , rawPayload - , signature: Nothing - } - - Log.info $ "Submitting package set update..." - result <- Run.liftAff $ submitPackageSetJob (registryApiUrl <> "/v1/package-sets") request - case result of - Left err -> do - Log.error $ "Failed to submit package set job: " <> err - Right { jobId } -> do - Log.info $ "Submitted package set job " <> unwrap jobId - --- | Find the latest version of each package uploaded within the time limit -findRecentUploads :: Hours -> Run PackageSetUpdaterEffects (Map PackageName Version) -findRecentUploads limit = do - allMetadata <- Registry.readAllMetadata - now <- nowUTC - - let - getLatestRecentVersion :: Metadata -> Maybe Version - getLatestRecentVersion (Metadata metadata) = do - let - recentVersions = Array.catMaybes $ flip map (Map.toUnfoldable metadata.published) - \(Tuple version { publishedTime }) -> - if (DateTime.diff now publishedTime) <= limit then Just version else Nothing - Array.last $ Array.sort recentVersions - - pure $ Map.fromFoldable $ Array.catMaybes $ flip map (Map.toUnfoldable allMetadata) \(Tuple name metadata) -> - map (Tuple name) $ getLatestRecentVersion metadata - --- | Submit a package set job to the registry API -submitPackageSetJob :: String -> Operation.PackageSetUpdateRequest -> Aff (Either String V1.JobCreatedResponse) -submitPackageSetJob url request = do - let body = JSON.print $ CJ.encode Operation.packageSetUpdateRequestCodec request - result <- Aff.attempt $ Fetch.fetch url - { method: POST - , headers: { "Content-Type": "application/json" } - , body - } - case result of - Left err -> pure $ Left $ "Network error: " <> Aff.message err - Right response -> do - responseBody <- response.text - if response.status >= 200 && response.status < 300 then - case JSON.parse responseBody >>= \json -> lmap CJ.DecodeError.print (CJ.decode V1.jobCreatedResponseCodec json) of - Left err -> pure $ Left $ "Failed to parse response: " <> err - Right r -> pure $ Right r - else - pure $ Left $ "HTTP " <> show response.status <> ": " <> responseBody diff --git a/scripts/src/PackageTransferrer.purs b/scripts/src/PackageTransferrer.purs deleted file mode 100644 index 0aac01fa1..000000000 --- a/scripts/src/PackageTransferrer.purs +++ /dev/null @@ -1,253 +0,0 @@ --- | This script checks for packages that have moved to a new GitHub location --- | and submits transfer jobs to update their registered location. --- | --- | Run via Nix: --- | nix run .#package-transferrer -- --dry-run # Log what would be submitted --- | nix run .#package-transferrer -- --submit # Actually submit to the API --- | --- | Required environment variables: --- | GITHUB_TOKEN - GitHub API token for fetching tags --- | PACCHETTIBOTTI_ED25519 - Private key for signing (only for --submit) --- | REGISTRY_API_URL - Registry API URL (default: https://registry.purescript.org) -module Registry.Scripts.PackageTransferrer where - -import Registry.App.Prelude - -import ArgParse.Basic (ArgParser) -import ArgParse.Basic as Arg -import Codec.JSON.DecodeError as CJ.DecodeError -import Data.Array as Array -import Data.Codec.JSON as CJ -import Data.Map as Map -import Data.String as String -import Effect.Aff as Aff -import Effect.Class.Console as Console -import Fetch (Method(..)) -import Fetch as Fetch -import JSON as JSON -import Node.Path as Path -import Node.Process as Process -import Registry.API.V1 as V1 -import Registry.App.Auth as Auth -import Registry.App.CLI.Git as Git -import Registry.App.Effect.Cache as Cache -import Registry.App.Effect.Env (RESOURCE_ENV) -import Registry.App.Effect.Env as Env -import Registry.App.Effect.GitHub (GITHUB) -import Registry.App.Effect.GitHub as GitHub -import Registry.App.Effect.Log (LOG) -import Registry.App.Effect.Log as Log -import Registry.App.Effect.Registry (REGISTRY) -import Registry.App.Effect.Registry as Registry -import Registry.Foreign.Octokit as Octokit -import Registry.Location (Location(..)) -import Registry.Operation (AuthenticatedPackageOperation(..)) -import Registry.Operation as Operation -import Registry.Operation.Validation as Operation.Validation -import Registry.PackageName as PackageName -import Run (AFF, EFFECT, Run) -import Run as Run -import Run.Except (EXCEPT) -import Run.Except as Except - -data Mode = DryRun | Submit - -derive instance Eq Mode - -parser :: ArgParser Mode -parser = Arg.choose "command" - [ Arg.flag [ "dry-run" ] - "Log what would be submitted without actually calling the API." - $> DryRun - , Arg.flag [ "submit" ] - "Submit transfer jobs to the registry API." - $> Submit - ] - -main :: Effect Unit -main = launchAff_ do - args <- Array.drop 2 <$> liftEffect Process.argv - - let description = "Check for moved packages and submit transfer jobs to the registry API." - mode <- case Arg.parseArgs "package-transferrer" description parser args of - Left err -> Console.log (Arg.printArgError err) *> liftEffect (Process.exit' 1) - Right command -> pure command - - Env.loadEnvFile ".env" - resourceEnv <- Env.lookupResourceEnv - token <- Env.lookupRequired Env.githubToken - - -- Only require pacchettibotti keys in submit mode - maybePrivateKey <- case mode of - DryRun -> pure Nothing - Submit -> Just <$> Env.lookupRequired Env.pacchettibottiED25519 - - githubCacheRef <- Cache.newCacheRef - registryCacheRef <- Cache.newCacheRef - let cache = Path.concat [ scratchDir, ".cache" ] - - octokit <- Octokit.newOctokit token resourceEnv.githubApiUrl - debouncer <- Registry.newDebouncer - - let - registryEnv :: Registry.RegistryEnv - registryEnv = - { pull: Git.Autostash - , write: Registry.ReadOnly - , repos: Registry.defaultRepos - , workdir: scratchDir - , debouncer - , cacheRef: registryCacheRef - } - - runPackageTransferrer mode maybePrivateKey resourceEnv.registryApiUrl - # Except.runExcept - # Registry.interpret (Registry.handle registryEnv) - # GitHub.interpret (GitHub.handle { octokit, cache, ref: githubCacheRef }) - # Log.interpret (Log.handleTerminal Normal) - # Env.runResourceEnv resourceEnv - # Run.runBaseAff' - >>= case _ of - Left err -> do - Console.error $ "Error: " <> err - liftEffect $ Process.exit' 1 - Right _ -> pure unit - -type PackageTransferrerEffects = (REGISTRY + GITHUB + LOG + RESOURCE_ENV + EXCEPT String + AFF + EFFECT + ()) - -runPackageTransferrer :: Mode -> Maybe String -> URL -> Run PackageTransferrerEffects Unit -runPackageTransferrer mode maybePrivateKey registryApiUrl = do - Log.info "Package Transferrer: checking for package transfers..." - allMetadata <- Registry.readAllMetadata - - -- Check each package for location changes - transfersNeeded <- Array.catMaybes <$> for (Map.toUnfoldable allMetadata) \(Tuple name (Metadata metadata)) -> - case metadata.location of - Git _ -> pure Nothing -- Skip non-GitHub packages - GitHub registered -> do - -- Fetch tags to see if repo has moved - GitHub.listTags { owner: registered.owner, repo: registered.repo } >>= case _ of - Left _ -> pure Nothing -- Can't fetch tags, skip - Right tags | Array.null tags -> pure Nothing -- No tags, skip - Right tags -> case Array.head tags of - Nothing -> pure Nothing - Just tag -> - -- Parse the tag URL to get actual current location - case tagUrlToRepoUrl tag.url of - Nothing -> pure Nothing - Just actual - | locationsMatch registered actual -> pure Nothing -- No change - | otherwise -> pure $ Just - { name - , newLocation: GitHub { owner: actual.owner, repo: actual.repo, subdir: registered.subdir } - } - - uniqueTransfers <- Array.catMaybes <$> for transfersNeeded \transfer@{ name, newLocation } -> - if Operation.Validation.locationIsUnique newLocation allMetadata then - pure $ Just transfer - else do - Log.warn $ Array.fold - [ "Skipping transfer for " - , PackageName.print name - , " because location " - , formatLocation newLocation - , " is already registered." - ] - pure Nothing - - case Array.length uniqueTransfers of - 0 -> Log.info "No packages require transferring." - n -> do - Log.info $ show n <> " packages need transferring" - for_ uniqueTransfers \{ name, newLocation } -> - submitTransferJob mode maybePrivateKey registryApiUrl name newLocation - --- | Parse GitHub API tag URL to extract owner/repo --- | Example: https://api.github.com/repos/octocat/Hello-World/commits/abc123 -tagUrlToRepoUrl :: String -> Maybe { owner :: String, repo :: String } -tagUrlToRepoUrl url = do - noPrefix <- String.stripPrefix (String.Pattern "https://api.github.com/repos/") url - case Array.take 2 $ String.split (String.Pattern "/") noPrefix of - [ owner, repo ] -> Just { owner, repo: String.toLower repo } - _ -> Nothing - --- | Case-insensitive comparison of GitHub locations -locationsMatch :: forall r. { owner :: String, repo :: String | r } -> { owner :: String, repo :: String } -> Boolean -locationsMatch loc1 loc2 = - String.toLower loc1.owner == String.toLower loc2.owner - && String.toLower loc1.repo - == String.toLower loc2.repo - --- | Submit a transfer job for a package that has moved -submitTransferJob - :: Mode - -> Maybe String - -> URL - -> PackageName - -> Location - -> Run PackageTransferrerEffects Unit -submitTransferJob mode maybePrivateKey registryApiUrl name newLocation = do - let - formatted = PackageName.print name - locationStr = formatLocation newLocation - - case mode of - DryRun -> do - Log.info $ "[DRY RUN] Would submit transfer job for " <> formatted <> " to " <> locationStr - - Submit -> do - privateKey <- case maybePrivateKey of - Nothing -> Except.throw "PACCHETTIBOTTI_ED25519 required for --submit mode" - Just pk -> pure pk - - let - payload :: Operation.TransferData - payload = { name, newLocation } - - rawPayload = JSON.print $ CJ.encode Operation.transferCodec payload - - -- Sign the payload with pacchettibotti keys - signature <- case Auth.signPayload { privateKey, rawPayload } of - Left err -> Except.throw $ "Error signing transfer for " <> formatted <> ": " <> err - Right sig -> pure sig - - let - authenticatedData :: Operation.AuthenticatedData - authenticatedData = - { payload: Transfer payload - , rawPayload - , signature - } - - Log.info $ "Submitting transfer job for " <> formatted - result <- Run.liftAff $ submitJob (registryApiUrl <> "/v1/transfer") authenticatedData - case result of - Left err -> - Log.error $ "Failed to submit transfer job for " <> formatted <> ": " <> err - Right { jobId } -> - Log.info $ "Submitted transfer job " <> unwrap jobId <> " for " <> formatted - -formatLocation :: Location -> String -formatLocation = case _ of - GitHub { owner, repo } -> owner <> "/" <> repo - Git { url } -> url - --- | Submit a transfer job to the registry API -submitJob :: String -> Operation.AuthenticatedData -> Aff (Either String V1.JobCreatedResponse) -submitJob url authData = do - let body = JSON.print $ CJ.encode Operation.authenticatedCodec authData - result <- Aff.attempt $ Fetch.fetch url - { method: POST - , headers: { "Content-Type": "application/json" } - , body - } - case result of - Left err -> pure $ Left $ "Network error: " <> Aff.message err - Right response -> do - responseBody <- response.text - if response.status >= 200 && response.status < 300 then - case JSON.parse responseBody >>= \json -> lmap CJ.DecodeError.print (CJ.decode V1.jobCreatedResponseCodec json) of - Left err -> pure $ Left $ "Failed to parse response: " <> err - Right r -> pure $ Right r - else - pure $ Left $ "HTTP " <> show response.status <> ": " <> responseBody diff --git a/scripts/src/VerifyIntegrity.purs b/scripts/src/VerifyIntegrity.purs deleted file mode 100644 index 226abe3d0..000000000 --- a/scripts/src/VerifyIntegrity.purs +++ /dev/null @@ -1,268 +0,0 @@ --- | Script for verifying that the registry and registry-index repos match each other and S3 --- | (or locally cached tarballs with --local) -module Registry.Scripts.VerifyIntegrity where - -import Registry.App.Prelude - -import ArgParse.Basic (ArgParser) -import ArgParse.Basic as Arg -import Control.Apply (lift2) -import Data.Array as Array -import Data.Either (isLeft) -import Data.Foldable (class Foldable, foldM, intercalate) -import Data.Formatter.DateTime as Formatter.DateTime -import Data.Map as Map -import Data.Set as Set -import Data.String as String -import Effect.Aff as Aff -import Effect.Class.Console (log) -import Effect.Class.Console as Console -import Node.FS.Aff as FS.Aff -import Node.FS.Stats as Stats -import Node.Path as Path -import Node.Process as Process -import Registry.App.CLI.Git as Git -import Registry.App.Effect.Cache as Cache -import Registry.App.Effect.Env as Env -import Registry.App.Effect.GitHub as GitHub -import Registry.App.Effect.Log (LOG) -import Registry.App.Effect.Log as Log -import Registry.App.Effect.Registry as Registry -import Registry.App.Effect.Storage as Storage -import Registry.Foreign.FSExtra as FS.Extra -import Registry.Foreign.Octokit as Octokit -import Registry.Internal.Format as Internal.Format -import Registry.ManifestIndex as ManifestIndex -import Registry.PackageName as PackageName -import Registry.Sha256 as Sha256 -import Registry.Version as Version -import Run (AFF, Run) -import Run as Run -import Run.Except (EXCEPT) -import Run.Except as Except - -parser :: ArgParser Boolean -parser = Arg.flag [ "--local" ] "Verify against local cache in scratch/ instead of S3" # Arg.boolean # Arg.default false - -main :: Effect Unit -main = launchAff_ do - args <- Array.drop 2 <$> liftEffect Process.argv - let description = "A script for verifying that the registry and registry-index repos match each other and S3 (or local cache with --local)." - localMode <- case Arg.parseArgs "verify-integrity" description parser args of - Left err -> Console.log (Arg.printArgError err) *> liftEffect (Process.exit' 1) - Right command -> pure command - - -- Environment - _ <- Env.loadEnvFile ".env" - resourceEnv <- Env.lookupResourceEnv - - -- Caching - let cache = Path.concat [ scratchDir, ".cache" ] - FS.Extra.ensureDirectory cache - githubCacheRef <- Cache.newCacheRef - registryCacheRef <- Cache.newCacheRef - - -- Registry - debouncer <- Registry.newDebouncer - let - registryEnv :: Registry.RegistryEnv - registryEnv = - { write: Registry.ReadOnly - , pull: Git.Autostash - , repos: Registry.defaultRepos - , workdir: scratchDir - , debouncer - , cacheRef: registryCacheRef - } - - -- Logging - now <- nowUTC - let logDir = Path.concat [ scratchDir, "logs" ] - FS.Extra.ensureDirectory logDir - let logFile = "verify-" <> String.take 19 (Formatter.DateTime.format Internal.Format.iso8601DateTime now) <> ".log" - let logPath = Path.concat [ logDir, logFile ] - log $ "Logs available at " <> logPath - - if localMode then do - token <- Env.lookupRequired Env.pacchettibottiToken - octokit <- Octokit.newOctokit token resourceEnv.githubApiUrl - - let - interpret = - Except.catch (\error -> Run.liftEffect (Console.log error *> Process.exit' 1)) - >>> Registry.interpret (Registry.handle registryEnv) - >>> GitHub.interpret (GitHub.handle { octokit, cache, ref: githubCacheRef }) - >>> Env.runResourceEnv resourceEnv - >>> Log.interpret (\log -> Log.handleTerminal Normal log *> Log.handleFs Verbose logPath log) - >>> Run.runBaseAff' - - interpret do - allMetadata <- Registry.readAllMetadata - allManifests <- Registry.readAllManifests - let - packages = Array.fromFoldable - $ Map.keys allMetadata - <> Map.keys (ManifestIndex.toMap allManifests) - Log.info $ "Verifying " <> show (Array.length packages) <> " packages (local mode)" - - results <- for packages \name -> do - result <- Except.runExcept do - verifyPackageLocal cache allMetadata allManifests name - result <$ case result of - Left err -> do - Log.error $ "Failed to verify " <> PackageName.print name <> ": " <> err - Right _ -> - Log.info $ "Verified " <> PackageName.print name - - Log.info "Finished." - when (any isLeft results) do - liftEffect $ Process.exit' 1 - - else do - token <- Env.lookupRequired Env.pacchettibottiToken - s3 <- lift2 { key: _, secret: _ } (Env.lookupRequired Env.spacesKey) (Env.lookupRequired Env.spacesSecret) - octokit <- Octokit.newOctokit token resourceEnv.githubApiUrl - - let - interpret = - Except.catch (\error -> Run.liftEffect (Console.log error *> Process.exit' 1)) - >>> Registry.interpret (Registry.handle registryEnv) - >>> Storage.interpret (Storage.handleS3 { s3, cache }) - >>> GitHub.interpret (GitHub.handle { octokit, cache, ref: githubCacheRef }) - >>> Env.runResourceEnv resourceEnv - >>> Log.interpret (\log -> Log.handleTerminal Normal log *> Log.handleFs Verbose logPath log) - >>> Run.runBaseAff' - - interpret do - allMetadata <- Registry.readAllMetadata - allManifests <- Registry.readAllManifests - let - packages = Array.fromFoldable - $ Map.keys allMetadata - <> Map.keys (ManifestIndex.toMap allManifests) - Log.info $ "Verifying " <> show (Array.length packages) <> " packages" - - results <- for packages \name -> do - result <- Except.runExcept do - published <- Storage.query name - verifyPackage allMetadata allManifests (Set.fromFoldable published) name - result <$ case result of - Left err -> do - Log.error $ "Failed to verify " <> PackageName.print name <> ": " <> err - Right _ -> - Log.info $ "Verified " <> PackageName.print name - - Log.info "Finished." - when (any isLeft results) do - liftEffect $ Process.exit' 1 - -intercalateMap :: forall f a d. Monoid d => Foldable f => d -> (a -> d) -> f a -> d -intercalateMap s f = intercalate s <<< map f <<< Array.fromFoldable - -setOf :: forall f a. Foldable f => (a -> String) -> f a -> String -setOf f vs = "{" <> intercalateMap "," f vs <> "}" - -dblDiff :: forall k. Ord k => Set k -> Set k -> Maybe { left :: Set k, right :: Set k } -dblDiff a b | a == b = Nothing -dblDiff a b = Just { left: Set.difference a b, right: Set.difference b a } - -printDblDiff :: forall k. (k -> String) -> { left :: String, right :: String } -> { left :: Set k, right :: Set k } -> String -printDblDiff f names { left, right } = intercalate ", " $ join - [ if Set.isEmpty right then mempty - else - [ names.left <> " missing " <> setOf f right ] - , if Set.isEmpty left then mempty - else - [ names.right <> " missing " <> setOf f left ] - ] - --- | Check that the metadata (from the registry repo), manifests (from --- | registry-index) and S3 storage all agree on what versions of the package --- | have been published. We have read the metadata and manifests from disk --- | above and queried S3 as well. We don't download and verify the hashes of --- | those resources match what is in the metadata and the manifests match, --- | but we could ... --- | --- | If there is a discrepancy, we will take the metadata to be the source of --- | truth, as the registry-index is just a cache of manifests that exist in --- | the tarballs. -verifyPackage :: forall r. Map PackageName Metadata -> ManifestIndex -> Set Version -> PackageName -> Run (EXCEPT String + LOG + r) Unit -verifyPackage allMetadata allManifests publishedS3 name = do - let formatted = PackageName.print name - Log.info $ "Checking versions for " <> formatted - metadataVersions <- case Map.lookup name allMetadata of - Nothing -> Except.throw $ "Missing metadata for " <> formatted - Just (Metadata { published }) -> pure $ Map.keys published - manifestVersions <- case Map.lookup name $ ManifestIndex.toMap allManifests of - Nothing -> - if Set.isEmpty metadataVersions then pure Set.empty - else Except.throw $ "Missing manifests for " <> formatted - Just manifests -> pure $ Map.keys manifests - - for_ (dblDiff metadataVersions manifestVersions) \diff -> do - Except.throw $ printDblDiff Version.print { left: "metadata", right: "manifests" } diff - let versions = Set.intersection metadataVersions manifestVersions - for_ (dblDiff publishedS3 versions) \diff -> do - Except.throw $ printDblDiff Version.print { left: "S3", right: "manifests/metadata" } diff - pure unit - --- | Verify a package using local cache instead of S3. Checks that: --- | 1. The list of package versions in metadata matches the manifest index --- | 2. Each cached tarball exists and has the correct size (bytes) --- | 3. Each cached tarball has the correct hash -verifyPackageLocal :: forall r. FilePath -> Map PackageName Metadata -> ManifestIndex -> PackageName -> Run (EXCEPT String + LOG + AFF + r) Unit -verifyPackageLocal cacheDir allMetadata allManifests name = do - let formatted = PackageName.print name - Log.info $ "Checking versions for " <> formatted <> " (local mode)" - - Metadata { published } <- case Map.lookup name allMetadata of - Nothing -> Except.throw $ "Missing metadata for " <> formatted - Just m -> pure m - - let metadataVersions = Map.keys published - - manifestVersions <- case Map.lookup name $ ManifestIndex.toMap allManifests of - Nothing -> - if Set.isEmpty metadataVersions then pure Set.empty - else Except.throw $ "Missing manifests for " <> formatted - Just manifests -> pure $ Map.keys manifests - - for_ (dblDiff metadataVersions manifestVersions) \diff -> do - Except.throw $ printDblDiff Version.print { left: "metadata", right: "manifests" } diff - - -- Collect cached tarball versions - let versions = Array.fromFoldable metadataVersions - cachedVersions <- Run.liftAff $ foldM (collectCached name) Set.empty versions - - for_ (dblDiff metadataVersions cachedVersions) \diff -> do - Except.throw $ printDblDiff Version.print { left: "metadata", right: "cache" } diff - - -- Verify integrity of each cached tarball - for_ versions \version -> do - let - tarballPath = Path.concat [ cacheDir, PackageName.print name <> "-" <> Version.print version ] - pkgVersion = formatPackageVersion name version - - case Map.lookup version published of - Nothing -> Except.throw $ "Version " <> Version.print version <> " not in metadata for " <> formatted - Just { bytes: expectedBytes, hash: expectedHash } -> do - -- Check file size - stats <- Run.liftAff $ FS.Aff.stat tarballPath - let actualBytes = Stats.size stats - unless (actualBytes == expectedBytes) do - Except.throw $ "Size mismatch for " <> pkgVersion <> ": expected " <> show expectedBytes <> " bytes, got " <> show actualBytes - - -- Check hash - actualHash <- Run.liftAff $ Sha256.hashFile tarballPath - unless (actualHash == expectedHash) do - Except.throw $ "Hash mismatch for " <> pkgVersion <> ": expected " <> Sha256.print expectedHash <> ", got " <> Sha256.print actualHash - - Log.debug $ "Verified integrity: " <> pkgVersion - where - collectCached :: PackageName -> Set Version -> Version -> Aff (Set Version) - collectCached pkgName acc version = do - let tarballPath = Path.concat [ cacheDir, PackageName.print pkgName <> "-" <> Version.print version ] - result <- Aff.attempt $ FS.Aff.stat tarballPath - pure $ case result of - Left _ -> acc - Right _ -> Set.insert version acc diff --git a/spago.lock b/spago.lock deleted file mode 100644 index d3b93a367..000000000 --- a/spago.lock +++ /dev/null @@ -1,3139 +0,0 @@ -{ - "workspace": { - "packages": { - "registry-app": { - "path": "app", - "core": { - "dependencies": [ - "aff", - "ansi", - "arrays", - "b64", - "bifunctors", - "codec", - "codec-json", - "console", - "const", - "control", - "datetime", - "dodo-printer", - "dotenv", - "effect", - "either", - "enums", - "exceptions", - "exists", - "fetch", - "filterable", - "foldable-traversable", - "foreign", - "foreign-object", - "formatters", - "http-methods", - "httpurple", - "identity", - "integers", - "js-fetch", - "js-promise-aff", - "js-uri", - "json", - "lists", - "maybe", - "newtype", - "node-buffer", - "node-child-process", - "node-execa", - "node-fs", - "node-path", - "node-process", - "now", - "nullable", - "numbers", - "ordered-collections", - "parallel", - "parsing", - "partial", - "prelude", - "profunctor", - "record", - "refs", - "registry-foreign", - "registry-lib", - "run", - "safe-coerce", - "strings", - "transformers", - "tuples", - "typelevel-prelude", - "unicode", - "unsafe-coerce", - "uuidv4" - ] - }, - "test": { - "dependencies": [ - "registry-test-utils", - "spec", - "spec-node" - ] - } - }, - "registry-app-e2e": { - "path": "app-e2e", - "core": { - "dependencies": [ - "aff", - "arrays", - "codec-json", - "console", - "datetime", - "exceptions", - "fetch", - "integers", - "json", - "node-child-process", - "node-execa", - "node-fs", - "node-path", - "node-process", - "ordered-collections", - "registry-app", - "registry-foreign", - "registry-lib", - "registry-scripts", - "registry-test-utils", - "routing-duplex", - "run", - "spec", - "spec-node", - "strings", - "transformers" - ] - }, - "test": { - "dependencies": [] - } - }, - "registry-dashboard": { - "path": "dashboard", - "core": { - "dependencies": [ - "aff", - "arrays", - "codec-json", - "const", - "control", - "datetime", - "effect", - "either", - "exceptions", - "fetch", - "foldable-traversable", - "formatters", - "halogen", - "halogen-subscriptions", - "integers", - "json", - "lists", - "maybe", - "newtype", - "now", - "parallel", - "prelude", - "registry-lib", - "routing-duplex", - "strings", - "tailrec", - "web-events", - "web-html", - "web-uievents" - ] - }, - "test": { - "dependencies": [] - } - }, - "registry-foreign": { - "path": "foreign", - "core": { - "dependencies": [ - "aff", - "aff-promise", - "arrays", - "b64", - "bifunctors", - "codec", - "codec-json", - "convertable-options", - "datetime", - "effect", - "either", - "exceptions", - "fetch", - "filterable", - "foldable-traversable", - "foreign-object", - "functions", - "http-methods", - "integers", - "js-date", - "js-fetch", - "json", - "maybe", - "newtype", - "node-buffer", - "node-path", - "nullable", - "ordered-collections", - "prelude", - "profunctor", - "registry-lib", - "strings", - "transformers", - "tuples", - "unsafe-coerce", - "variant" - ] - }, - "test": { - "dependencies": [ - "node-child-process", - "node-execa", - "node-fs", - "node-process", - "registry-test-utils", - "spec", - "spec-node" - ] - } - }, - "registry-lib": { - "path": "lib", - "core": { - "dependencies": [ - "aff", - "arrays", - "bifunctors", - "codec", - "codec-json", - "control", - "datetime", - "effect", - "either", - "exceptions", - "foldable-traversable", - "foreign-object", - "formatters", - "functions", - "functors", - "graphs", - "integers", - "json", - "language-cst-parser", - "lists", - "maybe", - "newtype", - "node-buffer", - "node-fs", - "node-path", - "nullable", - "ordered-collections", - "parsing", - "partial", - "prelude", - "profunctor", - "profunctor-lenses", - "routing-duplex", - "safe-coerce", - "st", - "strings", - "transformers", - "tuples" - ] - }, - "test": { - "dependencies": [ - "exceptions", - "json", - "node-child-process", - "node-execa", - "registry-test-utils", - "spec", - "spec-node" - ] - } - }, - "registry-scripts": { - "path": "scripts", - "core": { - "dependencies": [ - "aff", - "argparse-basic", - "arrays", - "codec-json", - "console", - "datetime", - "either", - "fetch", - "foldable-traversable", - "formatters", - "json", - "node-fs", - "node-path", - "node-process", - "ordered-collections", - "prelude", - "registry-app", - "registry-foreign", - "registry-lib", - "run", - "strings" - ] - }, - "test": { - "dependencies": [] - } - }, - "registry-test-utils": { - "path": "test-utils", - "core": { - "dependencies": [ - "arrays", - "bifunctors", - "codec-json", - "datetime", - "either", - "exceptions", - "foldable-traversable", - "formatters", - "json", - "maybe", - "ordered-collections", - "partial", - "prelude", - "registry-lib", - "spec", - "strings", - "transformers", - "tuples", - "unsafe-coerce" - ] - }, - "test": { - "dependencies": [] - } - } - }, - "package_set": { - "address": { - "registry": "68.1.1" - }, - "compiler": ">=0.15.15 <0.16.0", - "content": { - "abc-parser": "2.1.0", - "ace": "9.1.0", - "address-rfc2821": "0.1.1", - "aff": "8.0.0", - "aff-bus": "6.0.0", - "aff-coroutines": "9.0.0", - "aff-promise": "4.0.0", - "aff-retry": "2.0.0", - "affjax": "13.0.0", - "affjax-node": "1.0.0", - "affjax-web": "1.0.0", - "ansi": "7.0.0", - "apexcharts": "0.5.0", - "applicative-phases": "1.0.0", - "argonaut": "9.0.0", - "argonaut-aeson-generic": "0.4.1", - "argonaut-codecs": "9.1.0", - "argonaut-core": "7.0.0", - "argonaut-generic": "8.0.0", - "argonaut-traversals": "10.0.0", - "argparse-basic": "2.0.0", - "array-builder": "0.1.2", - "array-search": "0.6.0", - "arraybuffer": "13.2.0", - "arraybuffer-builder": "3.1.0", - "arraybuffer-types": "3.0.2", - "arrays": "7.3.0", - "arrays-extra": "0.6.1", - "arrays-zipper": "2.0.1", - "ask": "1.0.0", - "assert": "6.0.0", - "assert-multiple": "0.4.0", - "avar": "5.0.1", - "axon": "0.0.3", - "b64": "0.0.8", - "barbies": "1.0.1", - "barlow-lens": "0.9.0", - "benchlib": "0.0.4", - "bifunctors": "6.1.0", - "bigints": "7.0.1", - "blessed": "1.0.0", - "bolson": "0.3.9", - "bookhound": "0.1.7", - "bower-json": "3.0.0", - "call-by-name": "4.0.1", - "canvas": "6.0.0", - "canvas-action": "9.0.0", - "cartesian": "1.0.6", - "catenable-lists": "7.0.0", - "cbor-stream": "1.3.0", - "chameleon": "1.0.0", - "chameleon-halogen": "1.0.3", - "chameleon-react-basic": "1.1.0", - "chameleon-styled": "2.5.0", - "chameleon-transformers": "1.0.0", - "channel": "1.0.0", - "checked-exceptions": "3.1.1", - "choku": "1.0.2", - "classless": "0.1.1", - "classless-arbitrary": "0.1.1", - "classless-decode-json": "0.1.1", - "classless-encode-json": "0.1.3", - "classnames": "2.0.0", - "codec": "6.1.0", - "codec-argonaut": "10.0.0", - "codec-json": "2.0.0", - "colors": "7.0.1", - "concur-core": "0.5.0", - "concur-react": "0.5.0", - "concurrent-queues": "3.0.0", - "console": "6.1.0", - "const": "6.0.0", - "contravariant": "6.0.0", - "control": "6.0.0", - "convertable-options": "1.0.0", - "coroutines": "7.0.0", - "css": "6.0.0", - "css-class-name-extractor": "0.0.4", - "css-frameworks": "1.0.1", - "csv-stream": "2.3.0", - "data-mvc": "0.0.2", - "datetime": "6.1.0", - "datetime-parsing": "0.2.0", - "debounce": "0.1.0", - "debug": "6.0.2", - "decimals": "7.1.0", - "default-values": "1.0.1", - "deku": "0.9.23", - "deno": "0.0.5", - "dissect": "1.0.0", - "distributive": "6.0.0", - "dodo-printer": "2.2.3", - "dom-filereader": "7.0.0", - "dom-indexed": "13.0.0", - "dom-simple": "0.4.0", - "dotenv": "4.0.3", - "droplet": "0.6.0", - "dts": "1.0.0", - "dual-numbers": "1.0.3", - "dynamic-buffer": "3.0.1", - "echarts-simple": "0.0.1", - "effect": "4.0.0", - "either": "6.1.0", - "elmish": "0.13.0", - "elmish-enzyme": "0.1.1", - "elmish-hooks": "0.11.0", - "elmish-html": "0.10.0", - "elmish-testing-library": "0.3.2", - "elmish-time-machine": "0.4.2", - "email-validate": "7.0.0", - "encoding": "0.0.9", - "enums": "6.0.1", - "env-names": "0.4.0", - "environment": "2.0.0", - "error": "2.0.0", - "eta-conversion": "0.3.2", - "exceptions": "6.1.0", - "exists": "6.0.0", - "exitcodes": "4.0.0", - "expect-inferred": "3.0.0", - "express": "0.9.1", - "ezfetch": "1.1.1", - "fahrtwind": "2.0.0", - "fakerjs": "0.0.1", - "fallback": "0.1.0", - "fast-vect": "1.2.0", - "fetch": "4.1.0", - "fetch-argonaut": "1.0.1", - "fetch-core": "5.1.0", - "fetch-yoga-json": "1.1.0", - "ffi-simple": "0.5.1", - "fft": "0.3.0", - "fft-js": "0.1.0", - "filterable": "5.0.0", - "fix-functor": "0.1.0", - "fixed-points": "7.0.0", - "fixed-precision": "5.0.0", - "flame": "1.6.0", - "float32": "2.0.0", - "fmt": "0.2.1", - "foldable-traversable": "6.0.0", - "foldable-traversable-extra": "0.0.6", - "foreign": "7.0.0", - "foreign-object": "4.1.0", - "foreign-readwrite": "3.4.0", - "forgetmenot": "0.1.0", - "fork": "6.0.0", - "form-urlencoded": "7.0.0", - "formatters": "7.0.0", - "framer-motion": "1.0.1", - "free": "7.1.0", - "freeap": "7.0.0", - "freer-free": "0.0.1", - "freet": "7.0.0", - "functions": "6.0.0", - "functor1": "3.0.0", - "functors": "5.0.0", - "fuzzy": "0.4.0", - "gen": "4.0.0", - "generate-values": "1.0.1", - "generic-router": "0.0.1", - "geojson": "0.0.5", - "geometria": "2.2.0", - "gesso": "1.0.1", - "gojs": "0.1.1", - "golem-fetch": "0.1.0", - "grain": "3.0.0", - "grain-router": "3.0.0", - "grain-virtualized": "3.0.0", - "graphs": "8.1.0", - "group": "4.1.1", - "halogen": "7.0.0", - "halogen-bootstrap5": "5.3.2", - "halogen-canvas": "1.0.0", - "halogen-css": "10.0.0", - "halogen-declarative-canvas": "0.0.8", - "halogen-echarts-simple": "0.0.4", - "halogen-formless": "4.0.3", - "halogen-helix": "1.1.0", - "halogen-hooks": "0.6.3", - "halogen-hooks-extra": "0.9.0", - "halogen-infinite-scroll": "1.1.0", - "halogen-store": "0.5.4", - "halogen-storybook": "2.0.0", - "halogen-subscriptions": "2.0.0", - "halogen-svg-elems": "8.0.0", - "halogen-typewriter": "1.0.4", - "halogen-use-trigger-hooks": "1.0.0", - "halogen-vdom": "8.0.0", - "halogen-vdom-string-renderer": "0.5.0", - "halogen-xterm": "2.0.0", - "heckin": "2.0.1", - "heterogeneous": "0.7.0", - "homogeneous": "0.4.0", - "http-methods": "6.0.0", - "httpurple": "4.0.0", - "huffman": "0.4.0", - "humdrum": "0.0.1", - "hyrule": "2.3.8", - "identity": "6.0.0", - "identy": "4.0.1", - "indexed-db": "1.0.0", - "indexed-monad": "3.0.0", - "int64": "3.0.0", - "integers": "6.0.0", - "interpolate": "5.0.2", - "intersection-observer": "1.0.1", - "invariant": "6.0.0", - "jarilo": "1.0.1", - "jelly": "0.10.0", - "jelly-router": "0.3.0", - "jelly-signal": "0.4.0", - "jest": "1.0.0", - "js-abort-controller": "1.0.0", - "js-bigints": "2.2.1", - "js-date": "8.0.0", - "js-fetch": "0.2.1", - "js-fileio": "3.0.0", - "js-intl": "1.1.4", - "js-iterators": "0.1.1", - "js-maps": "0.1.2", - "js-promise": "1.0.0", - "js-promise-aff": "1.0.0", - "js-timers": "6.1.0", - "js-uri": "3.1.0", - "jsdom": "1.0.0", - "json": "1.1.0", - "json-codecs": "5.0.0", - "justifill": "0.5.0", - "jwt": "0.0.9", - "labeled-data": "0.2.0", - "language-cst-parser": "0.14.1", - "lazy": "6.0.0", - "lazy-joe": "1.0.0", - "lcg": "4.0.0", - "leibniz": "5.0.0", - "leveldb": "1.0.1", - "liminal": "1.0.1", - "linalg": "6.0.0", - "lists": "7.0.0", - "literals": "1.0.2", - "logging": "3.0.0", - "logging-journald": "0.4.0", - "lumi-components": "18.0.0", - "machines": "7.0.0", - "maps-eager": "0.5.0", - "marionette": "1.0.0", - "marionette-react-basic-hooks": "0.1.1", - "marked": "0.1.0", - "matrices": "5.0.1", - "matryoshka": "1.0.0", - "maybe": "6.0.0", - "media-types": "6.0.0", - "meowclient": "1.0.0", - "midi": "4.0.0", - "milkis": "9.0.0", - "mimetype": "0.0.1", - "minibench": "4.0.1", - "mmorph": "7.0.0", - "monad-control": "5.0.0", - "monad-logger": "1.3.1", - "monad-loops": "0.5.0", - "monad-unlift": "1.0.1", - "monoid-extras": "0.0.1", - "monoidal": "0.16.0", - "morello": "0.4.0", - "mote": "3.0.0", - "motsunabe": "2.0.0", - "mvc": "0.0.1", - "mysql": "6.0.1", - "n3": "0.1.0", - "nano-id": "1.1.0", - "nanoid": "0.1.0", - "naturals": "3.0.0", - "nested-functor": "0.2.1", - "newtype": "5.0.0", - "nextjs": "0.1.1", - "nextui": "0.2.0", - "node-buffer": "9.0.0", - "node-child-process": "11.1.0", - "node-event-emitter": "3.0.0", - "node-execa": "5.0.0", - "node-fs": "9.2.0", - "node-glob-basic": "2.0.0", - "node-http": "9.1.0", - "node-http2": "1.1.1", - "node-human-signals": "1.0.0", - "node-net": "5.1.0", - "node-os": "5.1.0", - "node-path": "5.0.1", - "node-process": "11.2.0", - "node-readline": "8.1.1", - "node-sqlite3": "8.0.0", - "node-stream-pipes": "2.1.6", - "node-streams": "9.0.1", - "node-tls": "0.3.1", - "node-url": "7.0.1", - "node-workerbees": "0.3.1", - "node-zlib": "0.4.0", - "nonempty": "7.0.0", - "now": "6.0.0", - "npm-package-json": "2.0.0", - "nullable": "6.0.0", - "numberfield": "0.2.2", - "numbers": "9.0.1", - "oak": "3.1.1", - "oak-debug": "1.2.2", - "object-maps": "0.3.0", - "ocarina": "1.5.4", - "oooooooooorrrrrrrmm-lib": "0.0.1", - "open-colors-scales-and-schemes": "1.0.0", - "open-drawing": "6.0.4", - "open-folds": "6.4.0", - "open-foreign-generic": "11.0.3", - "open-memoize": "6.2.0", - "open-mkdirp-aff": "1.2.0", - "open-pairing": "6.2.0", - "open-smolder": "12.0.2", - "options": "7.0.0", - "optparse": "5.0.1", - "ordered-collections": "3.2.0", - "ordered-set": "0.4.0", - "orders": "6.0.0", - "org-doc": "0.1.0", - "owoify": "1.2.0", - "pairs": "9.0.1", - "parallel": "7.0.0", - "parsing": "10.3.0", - "parsing-dataview": "3.2.4", - "partial": "4.0.0", - "pathy": "9.0.0", - "pha": "0.13.0", - "phaser": "0.7.0", - "phylio": "1.1.2", - "pipes": "8.0.0", - "pirates-charm": "0.0.1", - "play": "0.2.1", - "pmock": "0.9.0", - "point-free": "1.0.0", - "pointed-list": "0.5.1", - "polymorphic-vectors": "4.0.0", - "posix-types": "6.0.0", - "postgresql": "2.0.20", - "precise": "6.0.0", - "precise-datetime": "7.0.0", - "prelude": "6.0.2", - "prettier-printer": "3.0.0", - "printf": "0.1.0", - "priority-queue": "0.1.2", - "profunctor": "6.0.1", - "profunctor-lenses": "8.0.0", - "protobuf": "4.4.0", - "psa-utils": "8.0.0", - "psci-support": "6.0.0", - "punycode": "1.0.0", - "pursfmt": "0.16.0", - "qualified-do": "2.2.0", - "quantities": "12.2.0", - "quickcheck": "8.0.1", - "quickcheck-combinators": "0.1.3", - "quickcheck-laws": "7.0.0", - "quickcheck-utf8": "0.0.0", - "random": "6.0.0", - "rationals": "6.0.0", - "rdf": "0.1.0", - "react": "11.0.0", - "react-aria": "0.2.0", - "react-basic": "17.0.0", - "react-basic-classic": "3.0.0", - "react-basic-dnd": "10.1.0", - "react-basic-dom": "7.0.0", - "react-basic-dom-beta": "0.1.1", - "react-basic-emotion": "7.1.0", - "react-basic-hooks": "8.2.0", - "react-basic-storybook": "2.0.0", - "react-dom": "8.0.0", - "react-halo": "3.0.0", - "react-icons": "1.1.5", - "react-markdown": "0.1.0", - "react-testing-library": "4.0.1", - "react-virtuoso": "1.0.0", - "reactix": "0.6.1", - "read": "1.0.1", - "recharts": "1.1.0", - "record": "4.0.0", - "record-extra": "5.0.1", - "record-extra-srghma": "0.2.8", - "record-ptional-fields": "0.1.2", - "record-studio": "1.0.4", - "refs": "6.0.0", - "remotedata": "5.0.1", - "repr": "0.5.0", - "resize-arrays": "0.0.1", - "resize-observer": "1.0.0", - "resource": "2.0.1", - "resourcet": "1.0.0", - "result": "1.0.3", - "return": "0.2.0", - "ring-modules": "5.0.1", - "rito": "0.3.4", - "roman": "0.4.0", - "rough-notation": "1.0.2", - "routing": "11.0.0", - "routing-duplex": "0.7.0", - "run": "5.0.0", - "safe-coerce": "2.0.0", - "safely": "4.0.1", - "school-of-music": "1.3.0", - "selection-foldable": "0.2.0", - "selective-functors": "1.0.1", - "semirings": "7.0.0", - "shuffle": "2.0.0", - "signal": "13.0.0", - "simple-emitter": "3.0.1", - "simple-i18n": "2.0.1", - "simple-json": "9.0.0", - "simple-json-generics": "0.2.1", - "simple-ulid": "3.0.0", - "sized-matrices": "1.0.0", - "sized-vectors": "5.0.2", - "slug": "3.1.0", - "small-ffi": "4.0.1", - "soundfonts": "4.1.0", - "sparse-matrices": "2.0.1", - "sparse-polynomials": "3.0.1", - "spec": "8.1.1", - "spec-discovery": "8.4.0", - "spec-mocha": "5.1.1", - "spec-node": "0.0.3", - "spec-quickcheck": "5.0.2", - "spec-reporter-xunit": "0.7.1", - "splitmix": "2.1.0", - "ssrs": "1.0.0", - "st": "6.2.0", - "statistics": "0.3.2", - "strictlypositiveint": "1.0.1", - "string-parsers": "8.0.0", - "strings": "6.0.1", - "strings-extra": "4.0.0", - "stringutils": "0.0.12", - "structured-logging": "1.0.0", - "substitute": "0.2.3", - "supply": "0.2.0", - "svg-parser": "3.0.0", - "systemd-journald": "0.3.0", - "tagged": "4.0.2", - "tailrec": "6.1.0", - "tanstack-query": "2.0.0", - "tecton": "0.2.1", - "tecton-halogen": "0.2.0", - "test-unit": "17.0.0", - "text-formatting": "0.1.0", - "thermite": "6.3.1", - "thermite-dom": "0.3.1", - "these": "6.0.0", - "threading": "0.0.3", - "tidy": "0.11.1", - "tidy-codegen": "4.0.1", - "tldr": "0.0.0", - "toestand": "0.9.0", - "transformation-matrix": "1.0.1", - "transformers": "6.1.0", - "tree-rose": "4.0.2", - "trivial-unfold": "0.5.0", - "ts-bridge": "4.0.0", - "tuples": "7.0.0", - "two-or-more": "1.0.0", - "type-equality": "4.0.1", - "typedenv": "2.0.1", - "typelevel": "6.0.0", - "typelevel-lists": "2.1.0", - "typelevel-peano": "1.0.1", - "typelevel-prelude": "7.0.0", - "typelevel-regex": "0.0.3", - "typelevel-rows": "0.1.0", - "typisch": "0.4.0", - "uint": "7.0.0", - "ulid": "3.0.1", - "uncurried-transformers": "1.1.0", - "undefined": "2.0.0", - "undefined-is-not-a-problem": "1.1.0", - "unfoldable": "6.0.0", - "unicode": "6.0.0", - "unique": "0.6.1", - "unlift": "1.0.1", - "unordered-collections": "3.1.0", - "unsafe-coerce": "6.0.0", - "unsafe-reference": "5.0.0", - "untagged-to-tagged": "0.1.4", - "untagged-union": "1.0.0", - "uri": "9.0.0", - "url-immutable": "1.0.0", - "url-regex-safe": "0.1.1", - "uuid": "9.0.0", - "uuidv4": "1.0.0", - "validation": "6.0.0", - "variant": "8.0.0", - "variant-encodings": "2.0.0", - "variant-gen": "1.0.0", - "vectorfield": "1.0.1", - "vectors": "2.1.0", - "versions": "7.0.0", - "visx": "0.0.2", - "vitest": "1.0.0", - "web-clipboard": "6.0.0", - "web-cssom": "2.0.0", - "web-cssom-view": "0.1.0", - "web-dom": "6.0.0", - "web-dom-parser": "8.0.0", - "web-dom-xpath": "3.0.0", - "web-encoding": "3.0.0", - "web-events": "4.0.0", - "web-fetch": "4.0.1", - "web-file": "4.0.0", - "web-geometry": "0.1.0", - "web-html": "4.1.1", - "web-pointerevents": "2.0.0", - "web-proletarian": "1.0.0", - "web-promise": "3.2.0", - "web-resize-observer": "2.1.0", - "web-router": "1.0.0", - "web-socket": "4.0.0", - "web-storage": "5.0.0", - "web-streams": "4.0.0", - "web-touchevents": "4.0.0", - "web-uievents": "5.0.0", - "web-url": "2.0.0", - "web-workers": "1.1.0", - "web-xhr": "5.0.1", - "webextension-polyfill": "0.1.0", - "webgpu": "0.0.1", - "which": "2.0.0", - "whine-core": "0.0.32", - "xterm": "1.0.0", - "yaml-next": "3.1.1", - "yoga-fetch": "1.0.1", - "yoga-json": "5.1.0", - "yoga-om": "0.1.0", - "yoga-postgres": "6.0.0", - "yoga-react-dom": "1.0.1", - "yoga-subtlecrypto": "0.1.0", - "yoga-tree": "1.0.0", - "yoga-tree-svg": "0.1.0", - "yoga-tree-utils": "1.0.0", - "z3": "0.0.2", - "zipperarray": "2.0.0" - } - }, - "extra_packages": { - "dodo-printer": { - "repo": "https://github.com/natefaubion/purescript-dodo-printer.git", - "version": "v2.2.1", - "dependencies": [ - "aff", - "ansi", - "arrays", - "avar", - "console", - "control", - "effect", - "either", - "exceptions", - "foldable-traversable", - "integers", - "lists", - "maybe", - "newtype", - "parallel", - "partial", - "posix-types", - "prelude", - "safe-coerce", - "strings", - "tuples" - ] - }, - "language-cst-parser": { - "repo": "https://github.com/natefaubion/purescript-language-cst-parser.git", - "version": "v0.13.0", - "dependencies": [ - "arrays", - "console", - "const", - "control", - "effect", - "either", - "enums", - "foldable-traversable", - "free", - "functions", - "functors", - "identity", - "integers", - "lazy", - "lists", - "maybe", - "newtype", - "numbers", - "ordered-collections", - "partial", - "prelude", - "st", - "strings", - "transformers", - "tuples", - "typelevel-prelude", - "unfoldable", - "unsafe-coerce" - ] - } - } - }, - "packages": { - "aff": { - "type": "registry", - "version": "8.0.0", - "integrity": "sha256-9BRIrSdRoYybfiUmBqgKJ66cPqxYHb277bVWQMn/9sg=", - "dependencies": [ - "control", - "datetime", - "effect", - "either", - "exceptions", - "functions", - "newtype", - "parallel", - "partial", - "prelude", - "st", - "tailrec", - "transformers", - "unsafe-coerce" - ] - }, - "aff-promise": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-Jgp3y+NWuuAmwz2V3LlWoX+AvxqfVglY77N5zTw8xnI=", - "dependencies": [ - "aff", - "control", - "effect", - "either", - "exceptions", - "foreign", - "prelude", - "transformers" - ] - }, - "ansi": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-tOUaXJFzPSfBVAqLI8hc+Tz2M1rg16cXqyHaK/3SmFg=", - "dependencies": [ - "foldable-traversable", - "lists", - "prelude" - ] - }, - "argonaut-codecs": { - "type": "registry", - "version": "9.1.0", - "integrity": "sha256-K910SBrmYallESsm8pxJYThs7lig6hxX3M4PW33Cgew=", - "dependencies": [ - "argonaut-core", - "arrays", - "bifunctors", - "either", - "foldable-traversable", - "foreign-object", - "identity", - "integers", - "lists", - "maybe", - "nonempty", - "ordered-collections", - "prelude", - "record", - "strings", - "tuples" - ] - }, - "argonaut-core": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-gYZihtBIKthVuXXoiPphMookSf8M5aHyFYkcIWqlxTM=", - "dependencies": [ - "arrays", - "control", - "either", - "foreign-object", - "functions", - "gen", - "maybe", - "nonempty", - "prelude", - "strings", - "tailrec" - ] - }, - "argparse-basic": { - "type": "registry", - "version": "2.0.0", - "integrity": "sha256-j0HDYapdh4HCvJgt1xBF5Xp2ZU9Pyd5n6spEgPZYkDk=", - "dependencies": [ - "arrays", - "bifunctors", - "control", - "either", - "foldable-traversable", - "integers", - "lists", - "maybe", - "newtype", - "numbers", - "prelude", - "record", - "strings", - "tuples", - "unfoldable" - ] - }, - "arraybuffer-types": { - "type": "registry", - "version": "3.0.2", - "integrity": "sha256-p05cJnSkyeoB7VHzMyc2Eb1RwUaB7Nl0sQSqEGNEUD8=", - "dependencies": [] - }, - "arrays": { - "type": "registry", - "version": "7.3.0", - "integrity": "sha256-GD4z7LCi9wzi7FCQqbWhvs8paoUK9NO+HwgXZ+KP8Rk=", - "dependencies": [ - "bifunctors", - "control", - "foldable-traversable", - "functions", - "maybe", - "nonempty", - "partial", - "prelude", - "safe-coerce", - "st", - "tailrec", - "tuples", - "unfoldable", - "unsafe-coerce" - ] - }, - "avar": { - "type": "registry", - "version": "5.0.1", - "integrity": "sha256-8V4SxF4TIWZ9Ik1P4lPzIRUAZeFBe8w5WA2VcCEKsIk=", - "dependencies": [ - "aff", - "effect", - "either", - "exceptions", - "functions", - "maybe", - "prelude" - ] - }, - "b64": { - "type": "registry", - "version": "0.0.8", - "integrity": "sha256-YGa1p2zXZvw7iZ8VzumQsJ6JYLSOGmlwPwMwKQIVKVk=", - "dependencies": [ - "arraybuffer-types", - "either", - "encoding", - "enums", - "exceptions", - "functions", - "partial", - "prelude", - "strings" - ] - }, - "bifunctors": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-CvZRb8uI75eKIqiYntR+k05Wk6tKlMS5w8sCA2AFna0=", - "dependencies": [ - "const", - "either", - "newtype", - "prelude", - "tuples" - ] - }, - "catenable-lists": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-7+i/MHBLIRh604iCNT4ENWRWE/wuHIotER4kpdX9Ve0=", - "dependencies": [ - "control", - "foldable-traversable", - "lists", - "maybe", - "prelude", - "tuples", - "unfoldable" - ] - }, - "codec": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-WzfYiZsnqHJ/K4IliEEOkLLmsKozy8c7/fbrGZHASOk=", - "dependencies": [ - "bifunctors", - "control", - "invariant", - "prelude", - "profunctor", - "tuples" - ] - }, - "codec-json": { - "type": "registry", - "version": "2.0.0", - "integrity": "sha256-3WXgOm6lLi9vb/ZyKSuoa1o47mVdEmi7vYMX8vMVNAE=", - "dependencies": [ - "arrays", - "bifunctors", - "codec", - "control", - "either", - "foldable-traversable", - "foreign-object", - "integers", - "json", - "lists", - "maybe", - "newtype", - "ordered-collections", - "prelude", - "profunctor", - "record", - "safe-coerce", - "strings", - "transformers", - "tuples", - "type-equality", - "unsafe-coerce", - "variant" - ] - }, - "console": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-3T5PPz1mATZmV0C4GzkCkpLlCa5xG7u8ZNgnLrrrI1Q=", - "dependencies": [ - "effect", - "prelude" - ] - }, - "const": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-S+m0lQsxnji0z1XRUmkn+3RFuWJ5CXWuIr1u3i8Bfoc=", - "dependencies": [ - "invariant", - "newtype", - "prelude" - ] - }, - "contravariant": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-nuckEWqL80NaPmR/l9AUyrfycNh18XcvlZV9PAamwqc=", - "dependencies": [ - "const", - "either", - "newtype", - "prelude", - "tuples" - ] - }, - "control": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-0vxyYh5FL+ilxgLo954w1LqCUC0RSIivNNlb9CaNUQg=", - "dependencies": [ - "newtype", - "prelude" - ] - }, - "convertable-options": { - "type": "registry", - "version": "1.0.0", - "integrity": "sha256-X7jNa4e7T8f1VoOQUMnm/6GS6t9XZvWNsaey1c5ul/M=", - "dependencies": [ - "prelude", - "record" - ] - }, - "datetime": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-tbv6eDXy6QOD7YSknxYSbsDF32OC2JrNbSBpzMs7Doc=", - "dependencies": [ - "bifunctors", - "control", - "either", - "enums", - "foldable-traversable", - "functions", - "gen", - "integers", - "lists", - "maybe", - "newtype", - "numbers", - "ordered-collections", - "partial", - "prelude", - "tuples" - ] - }, - "distributive": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-netyJK4B32PDYHD0tF2AenKgo3urzbI770ycM0pArAo=", - "dependencies": [ - "identity", - "newtype", - "prelude", - "tuples", - "type-equality" - ] - }, - "dodo-printer": { - "type": "git", - "url": "https://github.com/natefaubion/purescript-dodo-printer.git", - "rev": "831c5c963a57ca4bfd62f96335267d7d0785851d", - "dependencies": [ - "aff", - "ansi", - "arrays", - "avar", - "console", - "control", - "effect", - "either", - "exceptions", - "foldable-traversable", - "integers", - "lists", - "maybe", - "newtype", - "parallel", - "partial", - "posix-types", - "prelude", - "safe-coerce", - "strings", - "tuples" - ] - }, - "dom-indexed": { - "type": "registry", - "version": "13.0.0", - "integrity": "sha256-Tr9uDQdmKN4TUGMg1/4KzsVu5zPa8OqKB4N6/S2a8vU=", - "dependencies": [ - "datetime", - "media-types", - "prelude", - "strings", - "web-clipboard", - "web-events", - "web-html", - "web-pointerevents", - "web-touchevents", - "web-uievents" - ] - }, - "dotenv": { - "type": "registry", - "version": "4.0.3", - "integrity": "sha256-qzLRn4J6m55FJQ1UmLlSBXEsstrI7QmgNAJhWBoLjD8=", - "dependencies": [ - "aff", - "arrays", - "control", - "effect", - "either", - "exceptions", - "foldable-traversable", - "lists", - "maybe", - "node-buffer", - "node-child-process", - "node-event-emitter", - "node-fs", - "node-os", - "node-process", - "node-streams", - "parsing", - "prelude", - "refs", - "run", - "strings", - "transformers", - "tuples", - "typelevel-prelude" - ] - }, - "effect": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-DdqU3bncRTjgMVPPRlAuGHVeajSPEcRaRhNuio7if6s=", - "dependencies": [ - "prelude" - ] - }, - "either": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-tBHx3PgtH4GZOApZCDkcM7szRTYJinrUrVGWazQCUUg=", - "dependencies": [ - "control", - "invariant", - "maybe", - "prelude" - ] - }, - "encoding": { - "type": "registry", - "version": "0.0.9", - "integrity": "sha256-VmELMMTv0r8C5ZABFHANoni7HZ7dX2yu5O83hSLZsSM=", - "dependencies": [ - "arraybuffer-types", - "either", - "exceptions", - "functions", - "prelude" - ] - }, - "enums": { - "type": "registry", - "version": "6.0.1", - "integrity": "sha256-sdZOmLX5+5pASGpvVyT0vSD5BBYQjZiayjV5XiPutso=", - "dependencies": [ - "control", - "either", - "gen", - "maybe", - "newtype", - "nonempty", - "partial", - "prelude", - "tuples", - "unfoldable" - ] - }, - "exceptions": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-nRrr+N0wk0TUFOWLR7e1n1oxcXo/X345bQUtoY9lW6A=", - "dependencies": [ - "effect", - "either", - "maybe", - "prelude" - ] - }, - "exists": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-vtLbrWNaI+pzx/2fw2AQnCovoLtcosClIhjO26QKkh8=", - "dependencies": [ - "unsafe-coerce" - ] - }, - "exitcodes": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-xBl4aFSEQ05Owp1ZvO6OxxvLK3LVzU35RSbj79SsePQ=", - "dependencies": [ - "enums", - "maybe", - "prelude" - ] - }, - "fetch": { - "type": "registry", - "version": "4.1.0", - "integrity": "sha256-AY3VFeK1Y0Mc6Gsr0DCXlbAn4MnhCXFW48EJW8XMdDI=", - "dependencies": [ - "aff", - "arraybuffer-types", - "bifunctors", - "effect", - "either", - "foreign", - "http-methods", - "js-fetch", - "js-promise", - "js-promise-aff", - "maybe", - "newtype", - "ordered-collections", - "prelude", - "record", - "strings", - "typelevel-prelude", - "web-file", - "web-streams" - ] - }, - "filterable": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-be7ci1Bzwud2waqoCtNGsTBZbt7fi+gCLvGddQQRJ3A=", - "dependencies": [ - "arrays", - "control", - "either", - "foldable-traversable", - "identity", - "lists", - "maybe", - "newtype", - "ordered-collections", - "prelude", - "st", - "tuples" - ] - }, - "foldable-traversable": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-NGGWyCio/xjce6fPP7y3wwg7CheqllID6aJudDxbWhA=", - "dependencies": [ - "bifunctors", - "const", - "control", - "either", - "functors", - "identity", - "maybe", - "newtype", - "orders", - "prelude", - "tuples" - ] - }, - "foreign": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-jiDRMVQfovGsbbA/FzbzYf6iWk9i2b5gNrIjz+gFfsI=", - "dependencies": [ - "either", - "functions", - "integers", - "lists", - "maybe", - "prelude", - "strings", - "transformers", - "unsafe-coerce" - ] - }, - "foreign-object": { - "type": "registry", - "version": "4.1.0", - "integrity": "sha256-x/Q7r80z/vmHRKvPwhYmHP/DnJciPtsyGfRTMzayKIU=", - "dependencies": [ - "arrays", - "foldable-traversable", - "functions", - "gen", - "lists", - "maybe", - "prelude", - "st", - "tailrec", - "tuples", - "typelevel-prelude", - "unfoldable", - "unsafe-coerce" - ] - }, - "fork": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-0keBVeZZg1eCZEy/En1CohVW829gQUqKtqBNIRa93/A=", - "dependencies": [ - "aff", - "prelude", - "transformers" - ] - }, - "formatters": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-ViGY8XoXmPFDPnuFHfgJzq8+UJshr8erfDOcY6fofRk=", - "dependencies": [ - "arrays", - "bifunctors", - "control", - "datetime", - "either", - "enums", - "foldable-traversable", - "integers", - "lists", - "maybe", - "newtype", - "numbers", - "ordered-collections", - "parsing", - "partial", - "prelude", - "strings", - "transformers", - "tuples" - ] - }, - "free": { - "type": "registry", - "version": "7.1.0", - "integrity": "sha256-tUBInfUpRQEw4u94GWcYW7EYdmSdDHSRn88a+C0eltU=", - "dependencies": [ - "catenable-lists", - "control", - "distributive", - "either", - "exists", - "foldable-traversable", - "invariant", - "lazy", - "maybe", - "prelude", - "tailrec", - "transformers", - "tuples", - "unsafe-coerce" - ] - }, - "freeap": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-e7SiV9rg+rVARsU9cxA/02sps7ubogvVgYwj8nWosX4=", - "dependencies": [ - "const", - "either", - "gen", - "lists", - "newtype", - "nonempty", - "prelude", - "tailrec", - "tuples", - "unsafe-coerce" - ] - }, - "functions": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-0jgtOr+SFO4UScKc40oA/9Vdcf2rLn3h3vZlY0KfqVo=", - "dependencies": [ - "prelude" - ] - }, - "functors": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-W1o/4wcp6S3chodYL8uixlXK2mfr1L+SzM1R+whqUco=", - "dependencies": [ - "bifunctors", - "const", - "contravariant", - "control", - "distributive", - "either", - "invariant", - "maybe", - "newtype", - "prelude", - "profunctor", - "tuples", - "unsafe-coerce" - ] - }, - "gen": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-kyOateeOtQa07vfTMYeSuCKdelgKE1R65fwKkZ10wsY=", - "dependencies": [ - "either", - "foldable-traversable", - "identity", - "maybe", - "newtype", - "nonempty", - "prelude", - "tailrec", - "tuples", - "unfoldable" - ] - }, - "graphs": { - "type": "registry", - "version": "8.1.0", - "integrity": "sha256-qllMIjmOGqwELOlXyHuiTT3YpeL/+gjaq7omAZ4npJQ=", - "dependencies": [ - "bifunctors", - "catenable-lists", - "foldable-traversable", - "lists", - "maybe", - "ordered-collections", - "prelude", - "tuples" - ] - }, - "halogen": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-YrUm27QEiE1DyGaGJ5MNLVg4H/P3gnX/NVv1gRtWjB4=", - "dependencies": [ - "aff", - "bifunctors", - "console", - "control", - "dom-indexed", - "effect", - "either", - "exceptions", - "foldable-traversable", - "foreign", - "fork", - "free", - "freeap", - "functions", - "halogen-subscriptions", - "halogen-vdom", - "lazy", - "lists", - "maybe", - "media-types", - "newtype", - "ordered-collections", - "parallel", - "prelude", - "profunctor", - "refs", - "strings", - "tailrec", - "transformers", - "tuples", - "unfoldable", - "unsafe-coerce", - "unsafe-reference", - "web-clipboard", - "web-dom", - "web-events", - "web-file", - "web-html", - "web-touchevents", - "web-uievents" - ] - }, - "halogen-subscriptions": { - "type": "registry", - "version": "2.0.0", - "integrity": "sha256-1eBtVZENgGtKuOY9H0iuYD3dO1CSqmOIyhYe4OhypOU=", - "dependencies": [ - "arrays", - "contravariant", - "control", - "effect", - "foldable-traversable", - "maybe", - "prelude", - "refs", - "safe-coerce", - "unsafe-reference" - ] - }, - "halogen-vdom": { - "type": "registry", - "version": "8.0.0", - "integrity": "sha256-jk6aj/tH630skFVk6mB5Q+0g2zb897KX72T63fJhJ2Y=", - "dependencies": [ - "arrays", - "bifunctors", - "effect", - "foreign", - "foreign-object", - "functions", - "maybe", - "newtype", - "nullable", - "prelude", - "refs", - "tuples", - "unsafe-coerce", - "web-dom", - "web-events" - ] - }, - "heterogeneous": { - "type": "registry", - "version": "0.7.0", - "integrity": "sha256-pQwcFvhWL8noktxaoD7FlLs2F2lYf3QHoyO0VOWO3I0=", - "dependencies": [ - "either", - "foldable-traversable", - "functors", - "prelude", - "record", - "tuples", - "variant" - ] - }, - "http-methods": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-S0UjrJSgwJxEsFHM/pX77dM8V4HfvvDqdHPckQrtxMs=", - "dependencies": [ - "either", - "prelude", - "strings" - ] - }, - "httpurple": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-N5ItjWiQFit0Cj3Ia1jvEDjwC6fIBcgvOmcoCyrMl/Q=", - "dependencies": [ - "aff", - "arrays", - "bifunctors", - "console", - "control", - "effect", - "either", - "exceptions", - "foldable-traversable", - "foreign-object", - "js-uri", - "justifill", - "literals", - "maybe", - "newtype", - "node-buffer", - "node-event-emitter", - "node-fs", - "node-http", - "node-net", - "node-process", - "node-streams", - "ordered-collections", - "posix-types", - "prelude", - "profunctor", - "record", - "record-studio", - "refs", - "routing-duplex", - "safe-coerce", - "strings", - "transformers", - "tuples", - "type-equality", - "typelevel-prelude", - "unsafe-coerce", - "untagged-union" - ] - }, - "identity": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-RY/iXPpxpqvKHYfJeFVNI1J06kohB9rbtWYuha8t0LE=", - "dependencies": [ - "control", - "invariant", - "newtype", - "prelude" - ] - }, - "integers": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-Y8yozC1uHRLcruobEyv5/bMKzE1BoKJ/olHv6o+bgm8=", - "dependencies": [ - "maybe", - "numbers", - "prelude" - ] - }, - "invariant": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-HyoT1I5qIHRq8RSZji6zZDvqnpVjLw748utrKwZtqsw=", - "dependencies": [ - "control", - "prelude" - ] - }, - "js-date": { - "type": "registry", - "version": "8.0.0", - "integrity": "sha256-DNQrp4xYc8o80jtxo0aOkUeXGi6h0Xidkk2ZQ8Y4l5Y=", - "dependencies": [ - "datetime", - "effect", - "enums", - "foreign", - "functions", - "integers", - "maybe", - "prelude" - ] - }, - "js-fetch": { - "type": "registry", - "version": "0.2.1", - "integrity": "sha256-jsMrMh8kIUW5bHbHl7hy8ev/iMru1MzfDa+hoAxo14Q=", - "dependencies": [ - "arraybuffer-types", - "arrays", - "effect", - "foldable-traversable", - "foreign", - "foreign-object", - "functions", - "http-methods", - "js-promise", - "maybe", - "newtype", - "prelude", - "record", - "tuples", - "typelevel-prelude", - "unfoldable", - "web-file", - "web-streams" - ] - }, - "js-promise": { - "type": "registry", - "version": "1.0.0", - "integrity": "sha256-u+6+ofIwT5XhT2fbMu4dShwkyWGfPRt9642Cnzw/Wvk=", - "dependencies": [ - "effect", - "exceptions", - "foldable-traversable", - "functions", - "maybe", - "newtype", - "prelude" - ] - }, - "js-promise-aff": { - "type": "registry", - "version": "1.0.0", - "integrity": "sha256-gEKAdNnTWjXS+tTp/AhYl/ezloKfuoQFcBKrdOx2USo=", - "dependencies": [ - "aff", - "control", - "effect", - "either", - "exceptions", - "foreign", - "js-promise", - "maybe", - "prelude", - "transformers" - ] - }, - "js-timers": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-mYEL4zjFOzrDeVCnR9Hg3pcZaBZilB6zPGaZ4fzRmzg=", - "dependencies": [ - "effect", - "prelude" - ] - }, - "js-uri": { - "type": "registry", - "version": "3.1.0", - "integrity": "sha256-7waHOctAfqhhw540FNJ7C/YsDMQW9TmA+SpHhduH6Uw=", - "dependencies": [ - "functions", - "maybe", - "prelude" - ] - }, - "json": { - "type": "registry", - "version": "1.1.0", - "integrity": "sha256-MOVxIlsRg8g9XICQqXTAxlP62LhL/t3U5FOWutF+5Eo=", - "dependencies": [ - "arrays", - "control", - "either", - "foldable-traversable", - "functions", - "gen", - "integers", - "maybe", - "nonempty", - "prelude", - "strings", - "tailrec", - "tuples", - "unfoldable" - ] - }, - "justifill": { - "type": "registry", - "version": "0.5.0", - "integrity": "sha256-IwoDO72+IROcUvFIwnronb/ny8SepssgTUG3Qp2TEKg=", - "dependencies": [ - "maybe", - "prelude", - "record", - "typelevel-prelude" - ] - }, - "language-cst-parser": { - "type": "git", - "url": "https://github.com/natefaubion/purescript-language-cst-parser.git", - "rev": "5621f2e9fa1a0df5930b95b26f922fa94c494e80", - "dependencies": [ - "arrays", - "console", - "const", - "control", - "effect", - "either", - "enums", - "foldable-traversable", - "free", - "functions", - "functors", - "identity", - "integers", - "lazy", - "lists", - "maybe", - "newtype", - "numbers", - "ordered-collections", - "partial", - "prelude", - "st", - "strings", - "transformers", - "tuples", - "typelevel-prelude", - "unfoldable", - "unsafe-coerce" - ] - }, - "lazy": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-MHRC+1Pc+AjortBp4b/e1e6KpEpsDwaJBNBB7TOL+1I=", - "dependencies": [ - "control", - "foldable-traversable", - "invariant", - "prelude" - ] - }, - "lists": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-/mGd7wLoU+wsTcqii7SSUByxsvwvjnu2PjwAD47yYb4=", - "dependencies": [ - "bifunctors", - "control", - "foldable-traversable", - "lazy", - "maybe", - "newtype", - "nonempty", - "partial", - "prelude", - "tailrec", - "tuples", - "unfoldable" - ] - }, - "literals": { - "type": "registry", - "version": "1.0.2", - "integrity": "sha256-ZkGRFD6gb8etYvyMYpX1Z7lx1Tql7R1VGnD1Dzinup8=", - "dependencies": [ - "integers", - "maybe", - "numbers", - "partial", - "prelude", - "typelevel-prelude", - "unsafe-coerce" - ] - }, - "maybe": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-8X6XEgtZ5lqfkEuc1hWOofnL6mwhk8gSJTxwvbR/SbY=", - "dependencies": [ - "control", - "invariant", - "newtype", - "prelude" - ] - }, - "media-types": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-3Wyq+vfqJFTmy1k6DGVnXStxtZxgK631wusDPzlFVxg=", - "dependencies": [ - "newtype", - "prelude" - ] - }, - "mmorph": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-Cfltaq+tfYjuSoSBN/b8cSQZbvZTqIRs6ebQewbIkGs=", - "dependencies": [ - "bifunctors", - "either", - "free", - "functors", - "identity", - "maybe", - "newtype", - "prelude", - "transformers", - "tuples" - ] - }, - "newtype": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-dt6w0cty4OS+Pjt9fsh3iU6ottqSU83mdAZkaEEvo6c=", - "dependencies": [ - "prelude", - "safe-coerce" - ] - }, - "node-buffer": { - "type": "registry", - "version": "9.0.0", - "integrity": "sha256-RdQjilmEHqsT4fwpCr8Mavw8KTafYrS3ZM6zeAAVAl8=", - "dependencies": [ - "arraybuffer-types", - "effect", - "functions", - "maybe", - "nullable", - "partial", - "prelude", - "st", - "unsafe-coerce" - ] - }, - "node-child-process": { - "type": "registry", - "version": "11.1.0", - "integrity": "sha256-Um7g75gXKEX51ZlviJWocTjUENUW1qHgqGqhYKkka74=", - "dependencies": [ - "aff", - "datetime", - "effect", - "either", - "exceptions", - "foreign", - "foreign-object", - "functions", - "maybe", - "node-buffer", - "node-event-emitter", - "node-fs", - "node-os", - "node-streams", - "nullable", - "parallel", - "partial", - "posix-types", - "prelude", - "refs", - "safe-coerce", - "unsafe-coerce" - ] - }, - "node-event-emitter": { - "type": "registry", - "version": "3.0.0", - "integrity": "sha256-wodx71NxJBPXOaawFn01V1ByYB2vT+I3RuV2giOVKMg=", - "dependencies": [ - "effect", - "either", - "functions", - "maybe", - "nullable", - "prelude", - "unsafe-coerce" - ] - }, - "node-execa": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-Br4KV8ZhK3dJYnl8VstEhQuJTFGd1yN2aJ72mCus1sg=", - "dependencies": [ - "aff", - "arrays", - "control", - "effect", - "either", - "exceptions", - "foldable-traversable", - "foreign", - "foreign-object", - "functions", - "integers", - "js-timers", - "maybe", - "node-buffer", - "node-child-process", - "node-event-emitter", - "node-fs", - "node-human-signals", - "node-os", - "node-path", - "node-process", - "node-streams", - "nullable", - "numbers", - "ordered-collections", - "parallel", - "parsing", - "partial", - "posix-types", - "prelude", - "record", - "refs", - "safe-coerce", - "strings", - "tailrec", - "tuples", - "unsafe-coerce", - "unsafe-reference" - ] - }, - "node-fs": { - "type": "registry", - "version": "9.2.0", - "integrity": "sha256-fxioTSm9y47WuYs9F/7nxCh/RVDCl3Kr3Hg779J4SJA=", - "dependencies": [ - "aff", - "datetime", - "effect", - "either", - "enums", - "exceptions", - "functions", - "integers", - "js-date", - "maybe", - "node-buffer", - "node-path", - "node-streams", - "nullable", - "partial", - "prelude", - "strings" - ] - }, - "node-http": { - "type": "registry", - "version": "9.1.0", - "integrity": "sha256-V6EFML/uqvBEUlQsph0VPuLQCtZdn51HJTa6yxh7YTk=", - "dependencies": [ - "arrays", - "datetime", - "effect", - "exceptions", - "foreign", - "foreign-object", - "maybe", - "node-buffer", - "node-event-emitter", - "node-net", - "node-streams", - "node-tls", - "node-url", - "nullable", - "prelude", - "unsafe-coerce" - ] - }, - "node-human-signals": { - "type": "registry", - "version": "1.0.0", - "integrity": "sha256-3Uet4N3iU8qXqVD5isYv54tS+v5fqd5zbhJ7ntQLjR4=", - "dependencies": [ - "arrays", - "control", - "foreign-object", - "maybe", - "ordered-collections", - "prelude" - ] - }, - "node-net": { - "type": "registry", - "version": "5.1.0", - "integrity": "sha256-e2kxHXolOzYD/OkZekYlVPwCl/b4WqyLOodA6QX7FQI=", - "dependencies": [ - "datetime", - "effect", - "exceptions", - "maybe", - "node-event-emitter", - "node-fs", - "node-streams", - "nullable", - "partial", - "prelude", - "unsafe-coerce" - ] - }, - "node-os": { - "type": "registry", - "version": "5.1.0", - "integrity": "sha256-UtQiPiZy5ouV832ubww/U8KlpVYny9xsW8WWfYOEgtA=", - "dependencies": [ - "arrays", - "bifunctors", - "control", - "datetime", - "effect", - "either", - "exceptions", - "foreign", - "foreign-object", - "functions", - "maybe", - "node-buffer", - "nullable", - "partial", - "posix-types", - "prelude", - "unsafe-coerce" - ] - }, - "node-path": { - "type": "registry", - "version": "5.0.1", - "integrity": "sha256-j7n/SmpVz0FOMJl4XZop3ofJ+MeIPkMNqUEUKHEL6Us=", - "dependencies": [ - "effect" - ] - }, - "node-process": { - "type": "registry", - "version": "11.2.0", - "integrity": "sha256-onzKeNmaeYfN3HSDPqSFTrrP2Yl9tnVsfKKhnf2plxM=", - "dependencies": [ - "effect", - "exceptions", - "foreign", - "foreign-object", - "maybe", - "node-event-emitter", - "node-streams", - "nullable", - "posix-types", - "prelude", - "strings" - ] - }, - "node-streams": { - "type": "registry", - "version": "9.0.1", - "integrity": "sha256-yuVGm/R/tBr4Nphi+a1a984Z0fkmN9Q5wWtSmbmJTpA=", - "dependencies": [ - "aff", - "arrays", - "effect", - "either", - "exceptions", - "maybe", - "node-buffer", - "node-event-emitter", - "nullable", - "prelude", - "refs", - "st", - "tailrec", - "unsafe-coerce" - ] - }, - "node-tls": { - "type": "registry", - "version": "0.3.1", - "integrity": "sha256-TRTFNDfY65UUN5dZO+00qwNc4Ab7cpj2mi7B0VIKq3w=", - "dependencies": [ - "effect", - "either", - "exceptions", - "foreign", - "maybe", - "node-buffer", - "node-event-emitter", - "node-net", - "nullable", - "partial", - "prelude", - "unsafe-coerce" - ] - }, - "node-url": { - "type": "registry", - "version": "7.0.1", - "integrity": "sha256-4h+vxO0RMrLN85hAQnoOjN6qD0jRIODhQcBIJXkat/4=", - "dependencies": [ - "effect", - "foreign", - "functions", - "maybe", - "nullable", - "prelude", - "tuples" - ] - }, - "nonempty": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-Ctkq8/+KiGqCE53Y/LvibA/coy7ev9HtRZU+GYS7yfQ=", - "dependencies": [ - "control", - "foldable-traversable", - "maybe", - "prelude", - "tuples", - "unfoldable" - ] - }, - "now": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-mTyG4s8tps3XIf0jjV/lME2/QlwMzrQly2wqfSSUM3o=", - "dependencies": [ - "datetime", - "effect", - "prelude" - ] - }, - "nullable": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-CDAZk+L9Sc0GCFTkE8n+qdp4Ys8avvnkU+m2hVALt7M=", - "dependencies": [ - "functions", - "maybe", - "prelude" - ] - }, - "numbers": { - "type": "registry", - "version": "9.0.1", - "integrity": "sha256-ZydYckgwRAnRaFC7pu6fADhzdwX+UqHZrfJyet64zls=", - "dependencies": [ - "functions", - "maybe", - "prelude" - ] - }, - "open-memoize": { - "type": "registry", - "version": "6.2.0", - "integrity": "sha256-yJLuVYX8FocHIJ+6q7efH/Pj4S+e1Y57DZ5Dn7qf4ww=", - "dependencies": [ - "either", - "integers", - "lazy", - "lists", - "maybe", - "partial", - "prelude", - "strings", - "tuples" - ] - }, - "optparse": { - "type": "registry", - "version": "5.0.1", - "integrity": "sha256-2btDjHPRR0uujUQJuZAN9AzvVoXe7yG6IwnY5evERhQ=", - "dependencies": [ - "arrays", - "bifunctors", - "control", - "effect", - "either", - "enums", - "exists", - "exitcodes", - "foldable-traversable", - "free", - "integers", - "lazy", - "lists", - "maybe", - "newtype", - "node-buffer", - "node-process", - "node-streams", - "nonempty", - "numbers", - "open-memoize", - "partial", - "prelude", - "strings", - "tailrec", - "transformers", - "tuples" - ] - }, - "ordered-collections": { - "type": "registry", - "version": "3.2.0", - "integrity": "sha256-NaEXc2cz/7lJfxPWEja4XlZbGxuRqJwFKQJjsJf51kQ=", - "dependencies": [ - "arrays", - "control", - "foldable-traversable", - "functions", - "gen", - "lists", - "maybe", - "newtype", - "partial", - "prelude", - "safe-coerce", - "tailrec", - "tuples", - "unfoldable" - ] - }, - "orders": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-nSMxZK7wxyDULkjZqvMyB71mVo6b0AyLLHosDVbv1XM=", - "dependencies": [ - "newtype", - "prelude" - ] - }, - "parallel": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-bAGpVhrFz2IyHkA4MR7+tE/19FZ7dzYBdYPgK5svkqw=", - "dependencies": [ - "control", - "effect", - "either", - "foldable-traversable", - "functors", - "maybe", - "newtype", - "prelude", - "profunctor", - "refs", - "transformers" - ] - }, - "parsing": { - "type": "registry", - "version": "10.3.0", - "integrity": "sha256-8gjwCqLq2mV8zlleVo7kTLQsjqSyDbVRGw51OSU0O9s=", - "dependencies": [ - "arrays", - "control", - "effect", - "either", - "enums", - "foldable-traversable", - "functions", - "identity", - "integers", - "lazy", - "lists", - "maybe", - "newtype", - "nullable", - "numbers", - "partial", - "prelude", - "st", - "strings", - "tailrec", - "transformers", - "tuples", - "unfoldable", - "unicode", - "unsafe-coerce" - ] - }, - "partial": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=", - "dependencies": [] - }, - "pipes": { - "type": "registry", - "version": "8.0.0", - "integrity": "sha256-dtUNpG8BGVr7pu6mQZyAnUAftUHgNS7f0QH3Kw5UTpQ=", - "dependencies": [ - "aff", - "control", - "effect", - "either", - "foldable-traversable", - "identity", - "lists", - "maybe", - "mmorph", - "newtype", - "prelude", - "tailrec", - "transformers", - "tuples" - ] - }, - "posix-types": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-Je5jG439F/SFO/IvVevZnWVxDIonrehlKuJkefP2lHE=", - "dependencies": [ - "maybe", - "newtype", - "prelude" - ] - }, - "prelude": { - "type": "registry", - "version": "6.0.2", - "integrity": "sha256-IqykrDRquGUD/LRSad7y75hZv3bUsGQ4knc8tdez8Qw=", - "dependencies": [] - }, - "profunctor": { - "type": "registry", - "version": "6.0.1", - "integrity": "sha256-Ow0mJC+PIIRq/a3mzCWY5vxuTgR/e+Ee68+mWeSRnWY=", - "dependencies": [ - "control", - "distributive", - "either", - "exists", - "invariant", - "newtype", - "prelude", - "tuples" - ] - }, - "profunctor-lenses": { - "type": "registry", - "version": "8.0.0", - "integrity": "sha256-mVIYR3kEMHyO2m+VicJeZeVyDJ4IgvkMzDll3JdnRN4=", - "dependencies": [ - "arrays", - "bifunctors", - "const", - "control", - "distributive", - "either", - "foldable-traversable", - "foreign-object", - "functors", - "identity", - "lists", - "maybe", - "newtype", - "ordered-collections", - "partial", - "prelude", - "profunctor", - "record", - "safe-coerce", - "transformers", - "tuples" - ] - }, - "random": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-7sZRo6P9UIiXDZ9s2QL9T8h3wlIwLdQrR4+oZKOijQw=", - "dependencies": [ - "effect", - "integers", - "prelude" - ] - }, - "record": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-WQ8fqp4X1wTA70J4qUcPW4Z7DCwx6e5KJnT/EuTdWSc=", - "dependencies": [ - "functions", - "prelude", - "unsafe-coerce" - ] - }, - "record-studio": { - "type": "registry", - "version": "1.0.4", - "integrity": "sha256-CRnFvSXpDDzio+buibUxFU9zPZDd7/lzrYC1Ya3FuIw=", - "dependencies": [ - "heterogeneous", - "lists", - "prelude", - "record", - "typelevel-prelude", - "unsafe-coerce" - ] - }, - "refs": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-KKD5G9dR2SauuDGULUJsxjqDOEi1Jqm+2Sq5U2mY8YU=", - "dependencies": [ - "effect", - "prelude" - ] - }, - "routing-duplex": { - "type": "registry", - "version": "0.7.0", - "integrity": "sha256-rQw9yC01qE/LS134zucIhLD6GFcVUzHX5HyzwosdGXM=", - "dependencies": [ - "arrays", - "control", - "either", - "foldable-traversable", - "integers", - "js-uri", - "lazy", - "maybe", - "newtype", - "prelude", - "profunctor", - "record", - "strings", - "tuples" - ] - }, - "run": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-zGCyG7IamjYWypKMXTP9PFis2ShnoZJKZiu3SbXMc+M=", - "dependencies": [ - "aff", - "control", - "effect", - "either", - "free", - "maybe", - "newtype", - "partial", - "prelude", - "tailrec", - "tuples", - "type-equality", - "typelevel-prelude", - "unsafe-coerce", - "variant" - ] - }, - "safe-coerce": { - "type": "registry", - "version": "2.0.0", - "integrity": "sha256-EJrxtKt5xC7TYjBZrSBO/J7Aw3DmtilYjt4olpAXWVc=", - "dependencies": [ - "unsafe-coerce" - ] - }, - "spec": { - "type": "registry", - "version": "8.1.1", - "integrity": "sha256-b6qjDb7lUJGLV+WEoywLu6BCXC4zGpfbN43mTL+vBeI=", - "dependencies": [ - "aff", - "ansi", - "arrays", - "avar", - "bifunctors", - "control", - "datetime", - "effect", - "either", - "exceptions", - "foldable-traversable", - "fork", - "identity", - "integers", - "lists", - "maybe", - "newtype", - "now", - "ordered-collections", - "parallel", - "pipes", - "prelude", - "refs", - "strings", - "tailrec", - "transformers", - "tuples" - ] - }, - "spec-node": { - "type": "registry", - "version": "0.0.3", - "integrity": "sha256-8/9+g0obbamV0YtqzmttbWs8HaWF7CEeW+eDtqzuAAo=", - "dependencies": [ - "aff", - "argonaut-codecs", - "argonaut-core", - "arrays", - "control", - "datetime", - "effect", - "either", - "foldable-traversable", - "identity", - "integers", - "maybe", - "newtype", - "node-buffer", - "node-fs", - "node-process", - "now", - "numbers", - "optparse", - "ordered-collections", - "partial", - "prelude", - "spec", - "strings", - "tuples" - ] - }, - "st": { - "type": "registry", - "version": "6.2.0", - "integrity": "sha256-WI4MEkkwUd4pnwZQ3G8VKWgX5t1RSNJludm5e9boaRo=", - "dependencies": [ - "effect", - "partial", - "prelude", - "tailrec", - "unsafe-coerce" - ] - }, - "strings": { - "type": "registry", - "version": "6.0.1", - "integrity": "sha256-FNto2hoNW5Da6W6crIk77YXMagBvOGHZE1kO7eZ1vLM=", - "dependencies": [ - "arrays", - "control", - "either", - "enums", - "foldable-traversable", - "gen", - "integers", - "maybe", - "newtype", - "nonempty", - "partial", - "prelude", - "tailrec", - "tuples", - "unfoldable", - "unsafe-coerce" - ] - }, - "tailrec": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-xrorCfP0xV1wubs27idQCHQqdvSUuqmmL/am7Ji1wVU=", - "dependencies": [ - "bifunctors", - "effect", - "either", - "identity", - "maybe", - "partial", - "prelude", - "refs" - ] - }, - "transformers": { - "type": "registry", - "version": "6.1.0", - "integrity": "sha256-QJmgNT/y7ljPHSsXaW4zxF/982BFiQ90KNL1g/NtEOQ=", - "dependencies": [ - "control", - "distributive", - "effect", - "either", - "exceptions", - "foldable-traversable", - "identity", - "lazy", - "maybe", - "newtype", - "prelude", - "st", - "tailrec", - "tuples", - "unfoldable" - ] - }, - "tuples": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-BKy7EfK8S1KeTCwyWqyW1wwcpSAQuh3IsczSVgS6fzY=", - "dependencies": [ - "control", - "invariant", - "prelude" - ] - }, - "type-equality": { - "type": "registry", - "version": "4.0.1", - "integrity": "sha256-BBqYOSnAKmayRvR5mtZoCf4SlYs2ipatD/+5nQx73aw=", - "dependencies": [] - }, - "typelevel-prelude": { - "type": "registry", - "version": "7.0.0", - "integrity": "sha256-+pHi14/40mTj/+lmCWqL0T7iXIXD1+NSg/KItyyoB0A=", - "dependencies": [ - "prelude", - "type-equality" - ] - }, - "unfoldable": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-BK/6By1sKp/ztk+9CF3q43SV0QDTLVdsGqFerKHSmbg=", - "dependencies": [ - "foldable-traversable", - "maybe", - "partial", - "prelude", - "tuples" - ] - }, - "unicode": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-5KugCNyYxscmZpnMbssOQA96XimuBN/bjHpdrtd5qaY=", - "dependencies": [ - "arrays", - "enums", - "integers", - "maybe", - "partial", - "prelude", - "strings", - "unsafe-coerce" - ] - }, - "unsafe-coerce": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-0L1QsaY20OILjfU5TV72d3U/tSjhmL9hJ32WMp857Lk=", - "dependencies": [] - }, - "unsafe-reference": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-ttSJTQUnK8AK2eGOsfRxLUJEDPL6pnmExjbiOqfwC6I=", - "dependencies": [ - "prelude" - ] - }, - "untagged-union": { - "type": "registry", - "version": "1.0.0", - "integrity": "sha256-MQvpb13TZEBoi9n/AoQ7ugniSQqbN6XQmAjii9pqJRk=", - "dependencies": [ - "either", - "foreign", - "foreign-object", - "literals", - "maybe", - "newtype", - "prelude", - "tuples", - "unsafe-coerce" - ] - }, - "uuidv4": { - "type": "registry", - "version": "1.0.0", - "integrity": "sha256-SW+Jpjp1DNSnHdueEV9EWgoPx4RWyyv8LxMjVlkrong=", - "dependencies": [ - "arrays", - "control", - "effect", - "foldable-traversable", - "integers", - "maybe", - "partial", - "prelude", - "random", - "strings" - ] - }, - "variant": { - "type": "registry", - "version": "8.0.0", - "integrity": "sha256-Ydrgl8liWEwYQOWW76PdZtg3vxKpoKgsnmFyTLgZkoU=", - "dependencies": [ - "control", - "enums", - "foldable-traversable", - "lists", - "maybe", - "partial", - "prelude", - "type-equality", - "unsafe-coerce" - ] - }, - "web-clipboard": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-3CV0txwKddLoj8qBrRjUWbiX0M8+rnOpi1iApr5PEb0=", - "dependencies": [ - "effect", - "functions", - "js-promise", - "maybe", - "nullable", - "prelude", - "unsafe-coerce", - "web-events", - "web-html" - ] - }, - "web-dom": { - "type": "registry", - "version": "6.0.0", - "integrity": "sha256-rVeqkxChkjqisDvOujy9BMEgGqJkwX3t6g21bXfiG5o=", - "dependencies": [ - "effect", - "enums", - "maybe", - "newtype", - "nullable", - "prelude", - "unsafe-coerce", - "web-events" - ] - }, - "web-events": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-wZNeldG0OU+8qGycUJ9e6vxf1/GC4Ep4OlRMhvjs9LA=", - "dependencies": [ - "datetime", - "effect", - "enums", - "foreign", - "functions", - "maybe", - "newtype", - "nullable", - "prelude", - "unsafe-coerce" - ] - }, - "web-file": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-txsdkeWBKU/+5v9m5c6kysgJQSDyV1r7MHaeT4STpjQ=", - "dependencies": [ - "datetime", - "effect", - "enums", - "foreign", - "integers", - "maybe", - "media-types", - "nullable", - "numbers", - "partial", - "prelude", - "tuples", - "unfoldable", - "unsafe-coerce", - "web-events" - ] - }, - "web-html": { - "type": "registry", - "version": "4.1.1", - "integrity": "sha256-yOuZJGUxFrivHKpo/jKuKzFZaq6PPPE64ftSysXFssk=", - "dependencies": [ - "effect", - "enums", - "foreign", - "functions", - "js-date", - "maybe", - "media-types", - "newtype", - "nullable", - "prelude", - "unsafe-coerce", - "web-dom", - "web-events", - "web-file", - "web-storage" - ] - }, - "web-pointerevents": { - "type": "registry", - "version": "2.0.0", - "integrity": "sha256-mQbm1V36u3It2WdYMAXtzLBJl3uRAhN4BjeZTw3LWN8=", - "dependencies": [ - "effect", - "maybe", - "prelude", - "unsafe-coerce", - "web-dom", - "web-events", - "web-html", - "web-uievents" - ] - }, - "web-storage": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-YJfos55oeKtBStNfoWfeflSy8j+4IDym5eaZtIY7YM4=", - "dependencies": [ - "effect", - "maybe", - "nullable", - "prelude", - "unsafe-coerce", - "web-events" - ] - }, - "web-streams": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-iKQbHh/3wNOWyGTZmWJZ1Yuri9J/wQIJKXcxrELaHVo=", - "dependencies": [ - "arraybuffer-types", - "effect", - "exceptions", - "js-promise", - "maybe", - "nullable", - "prelude", - "tuples" - ] - }, - "web-touchevents": { - "type": "registry", - "version": "4.0.0", - "integrity": "sha256-H02c53BJlHDlAgogj/x8bM7PagJjhMMrMPYy9I7Rf0s=", - "dependencies": [ - "functions", - "maybe", - "nullable", - "prelude", - "unsafe-coerce", - "web-events", - "web-uievents" - ] - }, - "web-uievents": { - "type": "registry", - "version": "5.0.0", - "integrity": "sha256-yh9uVLIGu8wE1+jwLehtfsRfR1eyiShhIMMhBPqKybE=", - "dependencies": [ - "effect", - "enums", - "maybe", - "nullable", - "prelude", - "unsafe-coerce", - "web-events", - "web-html" - ] - } - } -} diff --git a/spago.yaml b/spago.yaml deleted file mode 100644 index dabe26cc5..000000000 --- a/spago.yaml +++ /dev/null @@ -1,61 +0,0 @@ -workspace: - packageSet: - registry: 68.1.1 - extraPackages: - dodo-printer: - repo: https://github.com/natefaubion/purescript-dodo-printer.git - version: v2.2.1 - dependencies: - - aff - - ansi - - arrays - - avar - - console - - control - - effect - - either - - exceptions - - foldable-traversable - - integers - - lists - - maybe - - newtype - - parallel - - partial - - posix-types - - prelude - - safe-coerce - - strings - - tuples - language-cst-parser: - repo: https://github.com/natefaubion/purescript-language-cst-parser.git - version: v0.13.0 - dependencies: - - arrays - - console - - const - - control - - effect - - either - - enums - - foldable-traversable - - free - - functions - - functors - - identity - - integers - - lazy - - lists - - maybe - - newtype - - numbers - - ordered-collections - - partial - - prelude - - st - - strings - - transformers - - tuples - - typelevel-prelude - - unfoldable - - unsafe-coerce diff --git a/static/style.css b/static/style.css new file mode 100644 index 000000000..7c1205276 --- /dev/null +++ b/static/style.css @@ -0,0 +1,816 @@ +/* -- Custom Properties ------------------------------------------------ */ +:root { + --font-size-sm: 87.5%; +} + +/* -- Reset / Base ---------------------------------------------------- */ +*, *::before, *::after { box-sizing: border-box; } +html, body { height: 100%; margin: 0; padding: 0; } +body { + font-family: "Roboto", sans-serif; + font-size: var(--font-size-sm); + line-height: 1.563; + background-color: #ffffff; + color: #000000; +} +@media (min-width: 38em) { + body { font-size: 100%; } +} +@media (prefers-color-scheme: dark) { + body { background-color: #141417; color: #dedede; } +} + +a { color: #c4953a; font-weight: bold; text-decoration: none; } +a:visited { color: #c4953a; } +a:hover { color: #7b5904; } +@media (prefers-color-scheme: dark) { + a, a:visited { color: #d8ac55; } + a:hover { color: #f0dcab; } +} + +code, pre { + background-color: #f1f5f9; + border-radius: 3px; + color: #194a5b; + font-family: "Roboto Mono", monospace; + font-size: var(--font-size-sm); +} +@media (prefers-color-scheme: dark) { + code, pre { background-color: #232327; color: #c1d3da; } +} + +/* -- Layout ---------------------------------------------------------- */ +.container { + display: block; + max-width: 66em; + margin-left: auto; + margin-right: auto; + padding-left: 20px; + padding-right: 20px; +} +@media (min-width: 52em) { + .container { padding-left: 30px; padding-right: 30px; } +} +.clearfix::after { + content: ""; + display: table; + clear: both; +} + +/* -- Sticky Footer --------------------------------------------------- */ +.everything-except-footer { + min-height: 100%; + padding-bottom: 3.5em; +} +.footer { + position: relative; + height: 3.5em; + margin-top: -3.5em; + width: 100%; + background-color: #1d222d; + color: #f0f0f0; +} +.footer__inner { + display: flex; + align-items: center; + justify-content: space-between; + height: 3.5em; +} +.footer__label { + font-size: var(--font-size-sm); + font-weight: 300; + white-space: nowrap; +} +.footer__links { + list-style: none; + margin: 0; + padding: 0; + display: flex; + align-items: center; +} +.footer__links li { + display: inline-flex; + align-items: center; +} +.footer__links li + li::before { + content: "\00B7"; + color: #6b7280; + margin: 0 0.5em; +} +.footer__links a { + color: #f0f0f0; + font-weight: 400; + text-decoration: none; +} +.footer__links a:visited { + color: #f0f0f0; +} +.footer__links a:hover { + color: #d8ac55; +} +@media (max-width: 38em) { + .footer__inner { + flex-direction: column; + justify-content: center; + gap: 0.15em; + height: 3.5em; + } + .footer__label, .footer__links { + text-align: center; + } +} + +/* -- Top Banner ------------------------------------------------------- */ +.top-banner { + background-color: #1d222d; + color: #f0f0f0; + font-weight: normal; +} +.top-banner a { color: #f0f0f0; } +.top-banner a:hover { color: #d8ac55; } +.top-banner__logo { + float: left; + font-size: 2.44em; + font-weight: 300; + line-height: 90px; +} +@media (max-width: 38em) { + .top-banner__logo { font-size: 1.5em; line-height: 60px; } +} + +/* -- Tab Bar ---------------------------------------------------------- */ +.tab-bar { + background-color: #f8f9fa; + border-bottom: 1px solid #e0e0e0; +} +@media (prefers-color-scheme: dark) { + .tab-bar { background-color: #1a1a1f; border-bottom-color: #2a2a2f; } +} +.tab-bar__nav { + display: flex; + gap: 0; +} +.tab-bar__tab { + display: inline-block; + padding: 0.6em 1.25em; + font-size: var(--font-size-sm); + font-weight: 700; + color: #777; + text-decoration: none; + border-bottom: 2px solid transparent; + transition: color 0.15s ease, border-color 0.15s ease; +} +.tab-bar__tab:visited { color: #777; } +.tab-bar__tab:hover { + color: #c4953a; + border-bottom-color: #c4953a; +} +.tab-bar__tab--active { + color: #c4953a; + border-bottom-color: #c4953a; +} +.tab-bar__tab--active:visited { color: #c4953a; } +@media (prefers-color-scheme: dark) { + .tab-bar__tab { color: #a0a0a0; } + .tab-bar__tab:visited { color: #a0a0a0; } + .tab-bar__tab:hover { color: #d8ac55; border-bottom-color: #d8ac55; } + .tab-bar__tab--active { color: #d8ac55; border-bottom-color: #d8ac55; } + .tab-bar__tab--active:visited { color: #d8ac55; } +} + +/* -- Page Title ------------------------------------------------------- */ +.page-title { margin: 1.5em 0 1em; } +.page-title__title { + margin: 0; + font-size: 2em; + font-weight: 700; +} + +/* -- Toolbar ---------------------------------------------------------- */ +.jobs-toolbar { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 0; + margin-bottom: 1.5em; + padding: 0.75em 1em; + background-color: #f1f5f9; + border-radius: 3px; +} +@media (prefers-color-scheme: dark) { + .jobs-toolbar { background-color: #232327; } +} + +/* -- Toolbar zones ---------------------------------------------------- */ +.jobs-toolbar__zone { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 0.75em; +} +.jobs-toolbar__zone--query { flex: 0 0 auto; align-items: flex-start; } +.jobs-toolbar__zone--filters { flex: 1 1 0%; } +.jobs-toolbar__zone--actions { + flex: 0 0 auto; + align-items: flex-start; + gap: 0.5em; +} + +/* -- Zone dividers ---------------------------------------------------- */ +.jobs-toolbar__divider { + width: 1px; + align-self: stretch; + margin: 4px 12px; + background-color: #d0d5dd; +} +@media (prefers-color-scheme: dark) { + .jobs-toolbar__divider { background-color: #3a3a42; } +} + +/* -- Labeled field (label above control) ------------------------------ */ +.toolbar-field { + display: flex; + flex-direction: column; + gap: 2px; +} +.toolbar-field__label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #888; + white-space: nowrap; +} +@media (prefers-color-scheme: dark) { + .toolbar-field__label { color: #777; } +} + +/* -- Clear button ----------------------------------------------------- */ +.jobs-toolbar__clear { + font-family: "Roboto", sans-serif; + font-size: 75%; + font-weight: bold; + white-space: nowrap; + align-self: flex-end; + padding: 0 0 0.2em; + border: none; + background: none; + color: #c4953a; + cursor: pointer; +} +.jobs-toolbar__clear:hover { color: #7b5904; } +@media (prefers-color-scheme: dark) { + .jobs-toolbar__clear { color: #d8ac55; } + .jobs-toolbar__clear:hover { color: #f0dcab; } +} + +/* -- Toggle label (Auto-refresh) -------------------------------------- */ +.toolbar-toggle { + font-size: var(--font-size-sm); + font-weight: 700; + white-space: nowrap; + cursor: pointer; +} + +/* -- Custom range row (full width below toolbar) ---------------------- */ +.jobs-toolbar__custom-range { + display: flex; + flex-wrap: wrap; + gap: 0.5em; + align-items: center; + width: 100%; + padding-top: 0.5em; +} + +/* -- Form Controls ---------------------------------------------------- */ +.toolbar-select, .toolbar-input { + font-family: "Roboto", sans-serif; + font-size: var(--font-size-sm); + padding: 0.25em 0.5em; + border: 1px solid #ccc; + border-radius: 3px; + background-color: #ffffff; + color: #000000; + max-width: 130px; +} +.toolbar-select { max-width: none; } +@media (prefers-color-scheme: dark) { + .toolbar-select, .toolbar-input { + background-color: #141417; + color: #dedede; + border-color: #43434e; + } +} +.jobs-toolbar__custom-range .toolbar-input { + max-width: none; +} +.toolbar-btn { + font-family: "Roboto", sans-serif; + font-size: var(--font-size-sm); + padding: 0.25em 0.75em; + border: 1px solid #c4953a; + border-radius: 3px; + background-color: transparent; + color: #c4953a; + font-weight: 700; + cursor: pointer; +} +.toolbar-btn:hover { background-color: #c4953a; color: #ffffff; } +@media (prefers-color-scheme: dark) { + .toolbar-btn { border-color: #d8ac55; color: #d8ac55; } + .toolbar-btn:hover { background-color: #d8ac55; color: #141417; } +} +.toolbar-btn:disabled { opacity: 0.4; cursor: default; } +.toolbar-btn:disabled:hover { background-color: transparent; color: #c4953a; } +@media (prefers-color-scheme: dark) { + .toolbar-btn:disabled:hover { color: #d8ac55; } +} +.toolbar-btn--small { font-size: 75%; padding: 0.15em 0.5em; } + +/* -- Jobs Table -------------------------------------------------------- */ +.jobs-table { + width: 100%; + border-collapse: collapse; +} +.jobs-table__th { + text-align: left; + font-size: 75%; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #777; + padding: 0.5em 0.75em; + border-bottom: 2px solid #ddd; +} +@media (prefers-color-scheme: dark) { + .jobs-table__th { color: #a0a0a0; border-bottom-color: #43434e; } +} +.jobs-table__row { cursor: pointer; } +.jobs-table__row:hover { background-color: #f1f5f9; } +@media (prefers-color-scheme: dark) { + .jobs-table__row:hover { background-color: #232327; } +} +.jobs-table__td { + padding: 0.6em 0.75em; + border-bottom: 1px solid #eee; + vertical-align: middle; +} +@media (prefers-color-scheme: dark) { + .jobs-table__td { border-bottom-color: #2a2a2f; } +} + +/* -- Row Number Column ------------------------------------------------ */ +.jobs-table__th--rownum, +.jobs-table__td--rownum { + width: 3em; + text-align: right; + font-variant-numeric: tabular-nums; + color: #999; +} +@media (prefers-color-scheme: dark) { + .jobs-table__td--rownum { color: #666; } +} + +/* -- Sortable Column Header ------------------------------------------- */ +.jobs-table__th--sortable { + cursor: pointer; + user-select: none; +} +.jobs-table__th--sortable:hover { + color: #c4953a; +} +@media (prefers-color-scheme: dark) { + .jobs-table__th--sortable:hover { color: #d8ac55; } +} +.sort-indicator { + font-size: 75%; + margin-left: 0.15em; +} + +/* -- Job Type Badges -------------------------------------------------- */ +.job-type-badge { + display: inline-block; + font-family: "Roboto Mono", monospace; + font-size: 75%; + font-weight: 700; + padding: 0.15em 0.5em; + border-radius: 3px; + text-transform: uppercase; + letter-spacing: 0.03em; +} +.page-title .job-type-badge { + font-size: 40%; + padding: 0.15em 0.45em; + vertical-align: middle; + position: relative; + top: -0.1em; +} +.job-type-badge--publish { background-color: #e8f5e9; color: #2e7d32; } +.job-type-badge--unpublish { background-color: #fff3e0; color: #e65100; } +.job-type-badge--transfer { background-color: #e3f2fd; color: #1565c0; } +.job-type-badge--matrix { background-color: #f3e5f5; color: #7b1fa2; } +.job-type-badge--packageset { background-color: #fce4ec; color: #c62828; } +@media (prefers-color-scheme: dark) { + .job-type-badge--publish { background-color: #1b3a1e; color: #81c784; } + .job-type-badge--unpublish { background-color: #3e2100; color: #ffb74d; } + .job-type-badge--transfer { background-color: #0d2744; color: #64b5f6; } + .job-type-badge--matrix { background-color: #2a1233; color: #ce93d8; } + .job-type-badge--packageset { background-color: #3b1117; color: #ef9a9a; } +} + +/* -- Status Indicators ------------------------------------------------ */ +.job-status { + font-family: "Roboto Mono", monospace; + font-size: var(--font-size-sm); + font-weight: 700; +} +.job-status--pending { color: #777; } +.job-status--running { color: #1565c0; } +.job-status--succeeded { color: #2e7d32; } +.job-status--failed { color: #c62828; } +@media (prefers-color-scheme: dark) { + .job-status--pending { color: #a0a0a0; } + .job-status--running { color: #64b5f6; } + .job-status--succeeded { color: #81c784; } + .job-status--failed { color: #ef9a9a; } +} + +/* -- Running Status Pulse --------------------------------------------- */ +.job-status--running::before { + content: ""; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #1565c0; + margin-right: 0.4em; + animation: pulse 1.5s ease-in-out infinite; +} +@media (prefers-color-scheme: dark) { + .job-status--running::before { background-color: #64b5f6; } +} + +/* -- Job Package / Version -------------------------------------------- */ +.job-package { font-weight: 700; } +.job-version { + font-family: "Roboto Mono", monospace; + font-size: var(--font-size-sm); + color: #777; + margin-left: 0.25em; +} +@media (prefers-color-scheme: dark) { + .job-version { color: #a0a0a0; } +} + +/* -- Breadcrumb Bar --------------------------------------------------- */ +.breadcrumb-bar { + margin-top: 1em; + padding: 0.5em 0.85em; + background-color: #f1f5f9; + border-radius: 3px; + border: 1px solid #e2e8f0; +} +@media (prefers-color-scheme: dark) { + .breadcrumb-bar { + background-color: #1a1a1f; + border-color: #2a2a2f; + } +} +.breadcrumb-bar__inner { + display: flex; + align-items: center; + gap: 0.5em; + font-size: var(--font-size-sm); +} +.breadcrumb-bar__link { + font-weight: 700; +} +.breadcrumb-bar__sep { + color: #999; + font-weight: 300; + user-select: none; +} +@media (prefers-color-scheme: dark) { + .breadcrumb-bar__sep { color: #555; } +} +.breadcrumb-bar__current { + color: #555; + font-weight: 400; +} +@media (prefers-color-scheme: dark) { + .breadcrumb-bar__current { color: #a0a0a0; } +} +.breadcrumb-bar__current .job-type-badge { + font-size: 70%; + padding: 0.1em 0.4em; + vertical-align: middle; + position: relative; + top: -0.05em; +} + +/* -- Job Detail ------------------------------------------------------- */ +.job-detail__timestamps { + display: flex; + flex-direction: column; + gap: 0.35em; + margin-bottom: 1em; +} +.job-detail__ts-row { + display: flex; + align-items: baseline; + gap: 0.75em; +} +.job-detail__ts-label { + flex: 0 0 6.5em; + font-size: 75%; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #777; + font-weight: 700; + text-align: left; +} +@media (prefers-color-scheme: dark) { + .job-detail__ts-label { color: #a0a0a0; } +} +.job-detail__ts-value { + font-family: "Roboto Mono", monospace; + font-size: var(--font-size-sm); +} +.job-detail__section { + margin-bottom: 2em; +} +.job-detail__section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.75em; +} +.job-detail__section-title { + margin: 0; + font-size: 1.25em; + font-weight: 700; +} + +/* -- Log Controls ----------------------------------------------------- */ +.log-controls { + display: flex; + align-items: center; + gap: 0.75em; +} + +/* -- Log Viewer ------------------------------------------------------- */ +.log-table { width: 100%; border-collapse: collapse; table-layout: fixed; } +.log-table__th { + text-align: left; + font-size: 75%; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #777; + padding: 0.35em 0.75em 0.35em 0; + border-bottom: 2px solid #ddd; +} +@media (prefers-color-scheme: dark) { + .log-table__th { color: #a0a0a0; border-bottom-color: #43434e; } +} +.log-table__th--rownum { + width: 3em; + text-align: right; + padding-right: 0.75em; +} +.log-table__th--time { width: 13em; } +.log-entry__rownum { + font-family: "Roboto Mono", monospace; + font-size: 75%; + color: #999; + text-align: right; + white-space: nowrap; + padding: 0.25em 0.75em 0.25em 0; + vertical-align: baseline; + width: 3em; + font-variant-numeric: tabular-nums; +} +@media (prefers-color-scheme: dark) { + .log-entry__rownum { color: #666; } +} +.log-entry__time { + font-family: "Roboto Mono", monospace; + font-size: 75%; + color: #777; + white-space: normal; + padding: 0.25em 0.75em 0.25em 0; + vertical-align: baseline; + width: 13em; +} +@media (prefers-color-scheme: dark) { + .log-entry__time { color: #a0a0a0; } +} +.log-entry__message { + vertical-align: baseline; +} +/* Level indicated via left border on the row's first cell */ +.log-entry td:first-child { border-left: 5px solid transparent; } +.log-entry--DEBUG td:first-child { border-left-color: #777; } +.log-entry--INFO td:first-child { border-left-color: #1565c0; } +.log-entry--WARN td:first-child { border-left-color: #e65100; } +.log-entry--NOTICE td:first-child { border-left-color: #c4953a; } +.log-entry--ERROR td:first-child { border-left-color: #c62828; } +@media (prefers-color-scheme: dark) { + .log-entry--DEBUG td:first-child { border-left-color: #a0a0a0; } + .log-entry--INFO td:first-child { border-left-color: #64b5f6; } + .log-entry--WARN td:first-child { border-left-color: #ffb74d; } + .log-entry--NOTICE td:first-child { border-left-color: #d8ac55; } + .log-entry--ERROR td:first-child { border-left-color: #ef9a9a; } +} +/* Log level legend */ +.log-legend { + display: flex; + flex-wrap: wrap; + gap: 0.5em 1em; + font-size: var(--font-size-sm); + color: #777; + margin-bottom: 0.5em; +} +@media (prefers-color-scheme: dark) { + .log-legend { color: #a0a0a0; } +} +.log-legend__item { + display: flex; + align-items: center; + gap: 0.35em; +} +.log-legend__swatch { + display: inline-block; + width: 5px; + height: 1em; + border-radius: 1px; +} +.log-legend__swatch--DEBUG { background: #777; } +.log-legend__swatch--INFO { background: #1565c0; } +.log-legend__swatch--WARN { background: #e65100; } +.log-legend__swatch--NOTICE { background: #c4953a; } +.log-legend__swatch--ERROR { background: #c62828; } +@media (prefers-color-scheme: dark) { + .log-legend__swatch--DEBUG { background: #a0a0a0; } + .log-legend__swatch--INFO { background: #64b5f6; } + .log-legend__swatch--WARN { background: #ffb74d; } + .log-legend__swatch--NOTICE { background: #d8ac55; } + .log-legend__swatch--ERROR { background: #ef9a9a; } +} +.log-entry__text { + font-family: "Roboto Mono", monospace; + font-size: var(--font-size-sm); + margin: 0; + padding: 0; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; + background: transparent; + color: inherit; +} + +/* -- JSON Viewer ------------------------------------------------------ */ +.json-viewer { + background-color: #f1f5f9; + border-radius: 3px; + padding: 1em; + font-family: "Roboto Mono", monospace; + font-size: var(--font-size-sm); + color: #194a5b; + overflow-x: auto; +} +@media (prefers-color-scheme: dark) { + .json-viewer { background-color: #232327; color: #c1d3da; } +} + +/* -- Utility States --------------------------------------------------- */ +.loading-state, .empty-state, .error-state { + text-align: center; + padding: 3em 1em; + color: #777; +} +@media (prefers-color-scheme: dark) { + .loading-state, .empty-state, .error-state { color: #a0a0a0; } +} +.error-message { color: #c62828; } +@media (prefers-color-scheme: dark) { + .error-message { color: #ef9a9a; } +} + +/* -- Spinner ---------------------------------------------------------- */ +.spinner { + width: 24px; + height: 24px; + border: 3px solid #ddd; + border-top-color: #c4953a; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin: 0 auto 1em; +} +@media (prefers-color-scheme: dark) { + .spinner { border-color: #43434e; border-top-color: #d8ac55; } +} +.spinner--small { width: 16px; height: 16px; border-width: 2px; } + +/* -- Refresh Indicator ------------------------------------------------ */ +.refresh-indicator { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #2e7d32; + animation: pulse 1.5s ease-in-out infinite; +} +.refresh-indicator--inactive { + background-color: #ccc; + animation: none; +} +@media (prefers-color-scheme: dark) { + .refresh-indicator { background-color: #81c784; } + .refresh-indicator--inactive { background-color: #555; } +} + +/* -- Pagination Nav --------------------------------------------------- */ +.jobs-nav { + display: flex; + align-items: center; + justify-content: center; + gap: 1em; + margin-top: 1em; + padding-top: 1em; +} +.jobs-nav__info { + font-size: var(--font-size-sm); + color: #777; +} +@media (prefers-color-scheme: dark) { + .jobs-nav__info { color: #a0a0a0; } +} +.jobs-nav__btn { + font-family: "Roboto", sans-serif; + font-size: var(--font-size-sm); + padding: 0.25em 0.75em; + border: 1px solid #ccc; + border-radius: 3px; + background-color: transparent; + color: #555; + cursor: pointer; +} +.jobs-nav__btn:hover:not(:disabled) { + background-color: #f1f5f9; + color: #000; +} +.jobs-nav__btn:disabled { + opacity: 0.3; + cursor: default; +} +@media (prefers-color-scheme: dark) { + .jobs-nav__btn { + border-color: #43434e; + color: #a0a0a0; + } + .jobs-nav__btn:hover:not(:disabled) { + background-color: #232327; + color: #dedede; + } +} + +/* -- Animations ------------------------------------------------------- */ +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* -- Reduced Motion --------------------------------------------------- */ +@media (prefers-reduced-motion: reduce) { + .spinner, + .job-status--running::before, + .refresh-indicator { + animation: none; + } +} + +/* -- Responsive ------------------------------------------------------- */ +@media (max-width: 64em) { + .jobs-toolbar { + flex-direction: column; + align-items: stretch; + } + .jobs-toolbar__divider { + width: auto; + height: 1px; + align-self: auto; + margin: 8px 0; + } + .jobs-toolbar__zone--filters { flex-direction: row; } + .jobs-toolbar__zone--actions { justify-content: flex-end; } +} +@media (max-width: 38em) { + .jobs-toolbar__zone--filters { flex-direction: column; align-items: stretch; } + .jobs-table__th:nth-child(6), + .jobs-table__td:nth-child(6) { display: none; } + .job-detail__ts-label { flex: 0 0 5.5em; } + .log-table__th--time { width: 8em; text-align: right; } + .log-entry__time { width: 8em; text-align: right; } +} diff --git a/test-utils/spago.yaml b/test-utils/spago.yaml deleted file mode 100644 index 4362f8e77..000000000 --- a/test-utils/spago.yaml +++ /dev/null @@ -1,24 +0,0 @@ -package: - name: registry-test-utils - build: - pedanticPackages: true - dependencies: - - arrays - - bifunctors - - codec-json - - datetime - - either - - exceptions - - foldable-traversable - - formatters - - json - - maybe - - ordered-collections - - partial - - prelude - - registry-lib - - spec - - strings - - transformers - - tuples - - unsafe-coerce diff --git a/test-utils/src/Registry/Test/Assert.purs b/test-utils/src/Registry/Test/Assert.purs deleted file mode 100644 index 2d15e7a74..000000000 --- a/test-utils/src/Registry/Test/Assert.purs +++ /dev/null @@ -1,101 +0,0 @@ -module Registry.Test.Assert where - -import Prelude - -import Codec.JSON.DecodeError as CJ.DecodeError -import Control.Monad.Error.Class (class MonadThrow) -import Data.Array as Array -import Data.Bifunctor (lmap) -import Data.Codec.JSON as CJ -import Data.Either (Either(..)) -import Data.Foldable (class Foldable) -import Data.Foldable as Foldable -import Data.String as String -import Effect.Exception (Error) -import JSON as JSON -import Registry.Test.Utils as Utils -import Test.Spec.Assertions (AnyShow(..)) -import Test.Spec.Assertions as Assertions - -fail :: forall m. MonadThrow Error m => String -> m Unit -fail = Assertions.fail - -shouldEqual :: forall m a. MonadThrow Error m => Eq a => a -> a -> m Unit -shouldEqual a b = Assertions.shouldEqual (AnyShow a) (AnyShow b) - -shouldEqualRight :: forall m e a. MonadThrow Error m => Eq a => a -> Either e a -> m Unit -shouldEqualRight a = case _ of - Left e -> fail ("Expected Right, but received Left with value: " <> Utils.unsafeStringify e) - Right b -> shouldEqual a b - -shouldContain :: forall m f a. MonadThrow Error m => Eq a => Foldable f => f a -> a -> m Unit -shouldContain container elem = - when (elem `Foldable.notElem` container) do - fail (Utils.unsafeStringify elem <> "\n\nshould be a member of\n\n" <> Utils.unsafeStringify container) - -shouldNotContain :: forall m f a. MonadThrow Error m => Eq a => Foldable f => f a -> a -> m Unit -shouldNotContain container elem = - when (elem `Foldable.elem` container) do - fail (Utils.unsafeStringify elem <> "\n\nshould not be a member of\n\n" <> Utils.unsafeStringify container) - --- | Assert that all elements in `expected` are present in `actual`. --- | This is a subset check, not an equality check - `actual` may contain --- | additional elements. --- | --- | Useful for E2E tests where a shared database means we can't predict --- | exact contents, only that certain expected items are present. -shouldContainAll :: forall m a. MonadThrow Error m => Eq a => Array a -> Array a -> m Unit -shouldContainAll actual expected = - Foldable.for_ expected \elem -> - when (elem `Foldable.notElem` actual) do - fail ("Expected element not found:\n" <> Utils.unsafeStringify elem <> "\n\nin array:\n" <> Utils.unsafeStringify actual) - -shouldSatisfy :: forall m a. MonadThrow Error m => a -> (a -> Boolean) -> m Unit -shouldSatisfy a predicate = - unless (predicate a) do - fail (Utils.unsafeStringify a <> " doesn't satisfy predicate.") - -shouldNotSatisfy :: forall m a. MonadThrow Error m => a -> (a -> Boolean) -> m Unit -shouldNotSatisfy a predicate = - when (predicate a) do - fail (Utils.unsafeStringify a <> " satisfies predicate, but should not.") - -type Fixture = { label :: String, value :: String } - -shouldRoundTrip :: forall m a. MonadThrow Error m => String -> CJ.Codec a -> Array Fixture -> m Unit -shouldRoundTrip type_ codec fixtures = do - let - parseFixture { label, value } = - case lmap CJ.DecodeError.print <<< CJ.decode codec =<< JSON.parse value of - Left error -> Left { label, input: value, error } - Right result -> Right { label, input: value, result } - - fixtureParseResult = Utils.partitionEithers $ map parseFixture fixtures - - formatFixtureError { label, input, error } = label <> " failed with " <> error <> " for input:\n" <> input - - unless (Array.null fixtureParseResult.fail) do - fail $ String.joinWith "\n" - [ "Some well-formed " <> type_ <> " strings were not parsed correctly:" - , Array.foldMap (append "\n - " <<< formatFixtureError) fixtureParseResult.fail - ] - - let - roundtrip = fixtureParseResult.success <#> \fields -> do - let printed = JSON.printIndented $ CJ.encode codec fields.result - let input = String.trim fields.input - if input == printed then Right unit else Left { label: fields.label, input, printed } - - roundtripResult = Utils.partitionEithers roundtrip - - formatRoundtripError { label, input, printed } = - String.joinWith "\n" - [ label <> " input does not match output." - , String.joinWith "\n" [ input, "/=", printed ] - ] - - unless (Array.null roundtripResult.fail) do - fail $ String.joinWith "\n" - [ "Some well-formed " <> type_ <> " did not round-trip:" - , Array.foldMap (append "\n - " <<< formatRoundtripError) roundtripResult.fail - ] diff --git a/test-utils/src/Registry/Test/Fixtures.purs b/test-utils/src/Registry/Test/Fixtures.purs deleted file mode 100644 index 28692c13c..000000000 --- a/test-utils/src/Registry/Test/Fixtures.purs +++ /dev/null @@ -1,18 +0,0 @@ -module Registry.Test.Fixtures where - -import Prelude - -import Data.Either as Either -import Data.Maybe (Maybe(..)) -import Partial.Unsafe as Partial -import Registry.Location (Location(..)) -import Registry.Sha256 (Sha256) -import Registry.Sha256 as Sha256 - --- | A Location for use within tests. -defaultLocation :: Location -defaultLocation = GitHub { owner: "purescript", repo: "registry-dev", subdir: Nothing } - --- | A Sha256 for use within tests. -defaultHash :: Sha256 -defaultHash = Either.fromRight' (\_ -> Partial.unsafeCrashWith "Failed to parse Sha256") $ Sha256.parse "sha256-fN9RUAzN21ZY4Y0UwqUSxwUPVz1g7/pcqoDvbJZoT04=" diff --git a/test-utils/src/Registry/Test/Utils.js b/test-utils/src/Registry/Test/Utils.js deleted file mode 100644 index fbb240f53..000000000 --- a/test-utils/src/Registry/Test/Utils.js +++ /dev/null @@ -1,2 +0,0 @@ -export const archImpl = process.arch; -export const platformImpl = process.platform; diff --git a/test-utils/src/Registry/Test/Utils.purs b/test-utils/src/Registry/Test/Utils.purs deleted file mode 100644 index 57f177890..000000000 --- a/test-utils/src/Registry/Test/Utils.purs +++ /dev/null @@ -1,160 +0,0 @@ -module Registry.Test.Utils where - -import Prelude - -import Data.Array as Array -import Data.Array.NonEmpty as NonEmptyArray -import Data.Bifunctor (bimap) -import Data.DateTime as Date -import Data.DateTime as DateTime -import Data.Either (Either(..)) -import Data.Either as Either -import Data.Formatter.DateTime as DateTime.Formatters -import Data.Map as Map -import Data.Maybe (Maybe(..)) -import Data.Tuple (Tuple) -import JSON (JSON) -import JSON as JSON -import Partial.Unsafe (unsafeCrashWith) -import Partial.Unsafe as Partial -import Registry.Internal.Format as Internal.Format -import Registry.License as License -import Registry.Location (Location(..)) -import Registry.Manifest (Manifest(..)) -import Registry.PackageName (PackageName) -import Registry.PackageName as PackageName -import Registry.Range as Range -import Registry.SSH as SSH -import Registry.Sha256 as Sha256 -import Registry.Version (Version) -import Registry.Version as Version -import Unsafe.Coerce (unsafeCoerce) - -foreign import archImpl :: String -foreign import platformImpl :: String - --- | CPU architecture as reported by Node.js process.arch -data Arch = ARM64 | X64 | UnknownArch String - -derive instance Eq Arch - --- | Operating system platform as reported by Node.js process.platform -data Platform = Darwin | Linux | UnknownPlatform String - -derive instance Eq Platform - --- | Get the current CPU architecture -arch :: Arch -arch = case archImpl of - "arm64" -> ARM64 - "x64" -> X64 - other -> UnknownArch other - --- | Get the current OS platform -platform :: Platform -platform = case platformImpl of - "darwin" -> Darwin - "linux" -> Linux - other -> UnknownPlatform other - --- | Returns true if running on Apple Silicon (aarch64-darwin) -isAarch64Darwin :: Boolean -isAarch64Darwin = arch == ARM64 && platform == Darwin - --- | The minimum compiler version with native aarch64-darwin binaries. --- | Earlier versions only have x86_64-linux and x86_64-darwin builds. -minAarch64DarwinCompiler :: Version -minAarch64DarwinCompiler = unsafeVersion "0.15.9" - --- | Filter compiler versions to only those available on the current platform. --- | On aarch64-darwin, compilers before 0.15.9 don't have native binaries, --- | so tests for those versions should be skipped. -filterAvailableCompilers :: Array Version -> Array Version -filterAvailableCompilers versions - | isAarch64Darwin = Array.filter (\v -> v >= minAarch64DarwinCompiler) versions - | otherwise = versions - --- | Unsafely unpack the `Just` of an `Maybe`, given a message to crash with --- | if the value is a `Nothing`. -fromJust :: forall a. String -> Maybe a -> a -fromJust msg = case _ of - Nothing -> unsafeCrashWith msg - Just a -> a - --- | Unsafely unpack the `Right` of an `Either`, given a message to crash with --- | if the value is a `Left`. -fromRight :: forall a b. String -> Either a b -> b -fromRight msg = Either.fromRight' (\_ -> Partial.unsafeCrashWith msg) - --- | Unsafely stringify a value by coercing it to `Json` and stringifying it. -unsafeStringify :: forall a. a -> String -unsafeStringify a = JSON.print (unsafeCoerce a :: JSON) - --- | Partition an array of `Either` values into failure and success values -partitionEithers :: forall e a. Array (Either e a) -> { fail :: Array e, success :: Array a } -partitionEithers = Array.foldMap case _ of - Left err -> { fail: [ err ], success: [] } - Right res -> { fail: [], success: [ res ] } - --- | Unsafely parse a license from a string -unsafeNonEmptyArray :: forall a. Array a -> NonEmptyArray.NonEmptyArray a -unsafeNonEmptyArray arr = fromJust ("Failed to produce NonEmptyArray: " <> unsafeStringify arr) (NonEmptyArray.fromArray arr) - --- | Unsafely parse a sri-prefixed sha256 hash from a string -unsafeSha256 :: String -> Sha256.Sha256 -unsafeSha256 str = fromRight ("Failed to parse Sha256: " <> str) (Sha256.parse str) - --- | Unsafely parse a package name from a string -unsafePackageName :: String -> PackageName.PackageName -unsafePackageName str = fromRight ("Failed to parse PackageName: " <> str) (PackageName.parse str) - --- | Unsafely parse a version from a string -unsafeVersion :: String -> Version.Version -unsafeVersion str = fromRight ("Failed to parse Version: " <> str) (Version.parse str) - --- | Unsafely parse a range from a string -unsafeRange :: String -> Range.Range -unsafeRange str = fromRight ("Failed to parse Range: " <> str) (Range.parse str) - --- | Unsafely parse a license from a string -unsafeLicense :: String -> License.License -unsafeLicense str = fromRight ("Failed to parse License: " <> str) (License.parse str) - --- | Unsafely parse a DateTime from an ISO8601 datetime string -unsafeDateTime :: String -> DateTime.DateTime -unsafeDateTime str = fromRight ("Failed to parse DateTime: " <> str) (DateTime.Formatters.unformat Internal.Format.iso8601DateTime str) - --- | Unsafely parse a Date from an ISO8601 string -unsafeDate :: String -> Date.Date -unsafeDate str = fromRight ("Failed to parse Date: " <> str) (map Date.date $ DateTime.Formatters.unformat Internal.Format.iso8601Date str) - --- | Unsafely parse a public SSH key from a string -unsafeSSHPublicKey :: String -> SSH.PublicKey -unsafeSSHPublicKey str = fromRight ("Failed to parse SSH key: " <> str) (SSH.parsePublicKey str) - --- | Unsafely parse a private SSH key from a string -unsafeSSHPrivateKey :: String -> SSH.PrivateKey -unsafeSSHPrivateKey str = fromRight ("Failed to parse SSH key: " <> str) (SSH.parsePrivateKey { key: str, passphrase: Nothing }) - --- | Unsafely create a manifest from a name, version, and array of dependencies --- | where keys are package names and values are ranges. -unsafeManifest :: String -> String -> Array (Tuple String String) -> Manifest -unsafeManifest name version dependencies = Manifest - { name: unsafePackageName name - , version: unsafeVersion version - , dependencies: Map.fromFoldable $ map (bimap unsafePackageName unsafeRange) dependencies - , license: unsafeLicense "MIT" - , location: Git - { url: "https://github.com/purescript/purescript-" <> name <> ".git" - , subdir: Nothing - } - , ref: "v" <> version - , description: Nothing - , owners: Nothing - , includeFiles: Nothing - , excludeFiles: Nothing - } - --- | Format a package version as a string in the form 'name@X.Y.Z' -formatPackageVersion :: PackageName -> Version -> String -formatPackageVersion name version = PackageName.print name <> "@" <> Version.print version diff --git a/types/v1/Location.dhall b/types/v1/Location.dhall deleted file mode 100644 index 2ffde0e13..000000000 --- a/types/v1/Location.dhall +++ /dev/null @@ -1,7 +0,0 @@ -let Subdir = { subdir : Optional Text } - -let GitHub = Subdir //\\ { githubOwner : Text, githubRepo : Text } - -let Git = Subdir //\\ { gitUrl : Text } - -in < GitHub : GitHub | Git : Git > diff --git a/types/v1/Manifest.dhall b/types/v1/Manifest.dhall deleted file mode 100644 index 2f1a6fa5b..000000000 --- a/types/v1/Manifest.dhall +++ /dev/null @@ -1,24 +0,0 @@ -let Map = (./Prelude.dhall).Map.Type - -let PackageName = Text - -let License = Text - -let Version = Text - -let Range = Text - -let Manifest = - { name : PackageName - , license : License - , version : Version - , location : ./Location.dhall - , ref : Text - , owners : Optional (List ./Owner.dhall) - , description : Optional Text - , includeFiles : Optional (List Text) - , excludeFiles : Optional (List Text) - , dependencies : Optional (Map PackageName Range) - } - -in Manifest diff --git a/types/v1/Metadata.dhall b/types/v1/Metadata.dhall deleted file mode 100644 index 508964e38..000000000 --- a/types/v1/Metadata.dhall +++ /dev/null @@ -1,38 +0,0 @@ -let Map = (./Prelude.dhall).Map.Type -let NonEmpty = (./Prelude.dhall).NonEmpty.Type - -let Owner = ./Owner.dhall - -let Location = ./Location.dhall - -let Version = Text - -let Sha256 = Text - -let ISO8601String = Text - --- NOTE: The `ref` field is DEPRECATED and WILL BE REMOVED after 2027-01-31. --- It is only present for backwards compatibility with older package managers. --- Do not rely on its presence! -let PublishedMetadata = - { hash : Sha256 - , bytes : Natural - , publishedTime : ISO8601String - , compilers : NonEmpty Version - , ref : Optional Text - } - -let UnpublishedMetadata = - { reason : Text - , publishedTime : ISO8601String - , unpublishedTime : ISO8601String - } - -let Metadata = - { location : Location - , owners : Optional (List Owner) - , published : Map Version PublishedMetadata - , unpublished : Map Version UnpublishedMetadata - } - -in Metadata diff --git a/types/v1/Owner.dhall b/types/v1/Owner.dhall deleted file mode 100644 index 4ec19f148..000000000 --- a/types/v1/Owner.dhall +++ /dev/null @@ -1 +0,0 @@ -let Owner = { id : Optional Text, keytype : Text, public : Text } in Owner diff --git a/types/v1/Prelude.dhall b/types/v1/Prelude.dhall deleted file mode 100644 index d86e105e1..000000000 --- a/types/v1/Prelude.dhall +++ /dev/null @@ -1,5 +0,0 @@ --- We read DHALL_PRELUDE from the environment because we cannot make requests to --- remote hosts in an offline environment (such as Nix in CI). DHALL_PRELUDE is --- automatically set in your Nix shell, but if you are not using a Nix shell and --- want to run this locally then the URL will be used instead. -env:DHALL_PRELUDE diff --git a/types/v1/README.md b/types/v1/README.md deleted file mode 100644 index 71bb41334..000000000 --- a/types/v1/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Specs V1 - -The Dhall types in this directory represent core data types for the registry which the registry guarantees forwards compatibility for. Those include: - -- `Manifest.dhall`: the `purs.json` manifest format -- `Metadata.dhall`: the package metadata file format - -as well as other types required by these two file formats. To learn more, please see the [SPEC.md](../../SPEC.md).