diff --git a/.github/workflows/deno_tests.yml b/.github/workflows/deno_tests.yml deleted file mode 100644 index 0dc8eea91..000000000 --- a/.github/workflows/deno_tests.yml +++ /dev/null @@ -1,99 +0,0 @@ ---- -name: Deno build - -on: - push: - branches: [master] - tags: ['*'] - pull_request: - branches: [master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Save describe stamp - id: describe - run: echo version=$( git describe ) >> $GITHUB_OUTPUT - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - run: ./build.ts - working-directory: ./bids-validator - - run: deno run -A ./bids-validator/dist/validator/bids-validator.js --version - - uses: actions/upload-artifact@v4 - with: - name: main - path: bids-validator/dist/validator - - test: - runs-on: ${{ matrix.os }} - timeout-minutes: 5 - strategy: - matrix: - os: [ubuntu-22.04, macos-12, windows-2022] - allow-net: [true, false] - fail-fast: false - defaults: - run: - working-directory: ./bids-validator - shell: bash - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - name: Set permissions with network access - run: echo 'PERMS=--allow-read --allow-write --allow-env --allow-run --allow-net' >> $GITHUB_ENV - if: ${{ matrix.allow-net }} - - name: Set permissions without network access - run: echo 'PERMS=--allow-read --allow-write --allow-env --allow-run --deny-net' >> $GITHUB_ENV - if: ${{ ! matrix.allow-net }} - - run: deno test $PERMS --coverage=cov/ src/ - - name: Collect coverage - run: deno coverage cov/ --lcov --output=coverage.lcov - if: ${{ always() }} - - uses: codecov/codecov-action@v4 - if: ${{ always() }} - with: - files: coverage.lcov - - deploy: - needs: [build] - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' && github.repository_owner == 'bids-standard' - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PUSH_TOKEN }} - - name: Set credentials - run: | - git config --global user.name "BIDS-Bot" - git config --global user.email "bids-maintenance@users.noreply.github.com" - - name: Reset deno-build to orphan - run: | - git checkout --orphan deno-build - git reset --hard - - uses: actions/download-artifact@v4 - with: - name: main - path: main - - name: Commit to new branch - run: | - mv main/main.js main/bids-validator.js . - git add main.js bids-validator.js - git commit -m "BLD: $VERSION [skip ci]" || true - env: - VERSION: ${{ needs.build.describe.version }} - - name: Push - run: git push -f origin deno-build diff --git a/.github/workflows/schema_web_build.yml b/.github/workflows/schema_web_build.yml deleted file mode 100644 index 97f340216..000000000 --- a/.github/workflows/schema_web_build.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Schema validator web build - -on: - push: - branches: [master] - tags: ['*'] - pull_request: - branches: [master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - run: deno task build - working-directory: ./web - - uses: actions/upload-artifact@v4 - with: - name: web - path: web/dist diff --git a/.github/workflows/schema_web_deploy.yml b/.github/workflows/web_validator.yml similarity index 61% rename from .github/workflows/schema_web_deploy.yml rename to .github/workflows/web_validator.yml index 2778bf4e5..19f29713a 100644 --- a/.github/workflows/schema_web_deploy.yml +++ b/.github/workflows/web_validator.yml @@ -1,9 +1,20 @@ --- -name: Schema validator web deploy +name: Web validator build and deploy on: + push: + branches: [master, main] + pull_request: + branches: [master, main] release: types: [published] + workflow_dispatch: + inputs: + deploy: + description: Deploy to github-pages + required: false + type: boolean + concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -11,41 +22,34 @@ concurrency: jobs: build: + name: Build web validator runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # Use Node 18 for legacy build - uses: actions/setup-node@v4 with: node-version: 18 - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - run: deno task build - working-directory: ./web - name: Install NPM deps run: npm install - name: Build legacy validator website run: npm run web-export - - name: Move legacy validator build into deno website - run: mv bids-validator-web/out web/dist/legacy - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@v3 with: - path: web/dist + path: bids-validator-web/out deploy: + name: Deploy web validator needs: build + if: github.event_name == 'release' || inputs.deploy permissions: contents: read pages: write id-token: write - # Deploy to the github-pages environment environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - # Specify runner + deployment step runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action + uses: actions/deploy-pages@v4 diff --git a/bids-validator/bids-validator-deno b/bids-validator/bids-validator-deno deleted file mode 100755 index 644fc7564..000000000 --- a/bids-validator/bids-validator-deno +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env --allow-net --allow-run -import './src/bids-validator.ts' - diff --git a/bids-validator/build.ts b/bids-validator/build.ts deleted file mode 100755 index 863ecd85b..000000000 --- a/bids-validator/build.ts +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env --allow-net --allow-run --reload -/** - * Build the schema based validator for distribution (web and npm), targets browser compatible ESM - * - * If you would like to use this package in a Node.js project, you'll need to use native ESM or a transform system - */ -import * as esbuild from 'https://deno.land/x/esbuild@v0.24.0/mod.js' -import { parse } from 'https://deno.land/std@0.223.0/flags/mod.ts' -import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@0.10.3" -import * as path from "https://deno.land/std@0.223.0/path/mod.ts" -import { getVersion } from './src/version.ts' - - -function getModuleDir(importMeta: ImportMeta): string { - return path.resolve(path.dirname(path.fromFileUrl(importMeta.url))); -} - -const dir = getModuleDir(import.meta); - -const MAIN_ENTRY = path.join(dir, 'src', 'main.ts') -const CLI_ENTRY = path.join(dir, 'src', 'bids-validator.ts') - -const flags = parse(Deno.args, { - boolean: ['minify'], - default: { minify: false }, -}) - -const version = await getVersion() - -const versionPlugin = { - name: 'version', - setup(build: esbuild.PluginBuild) { - build.onResolve({ filter: /\.git-meta\.json/ }, (args) => ({ - path: args.path, - namespace: 'version-ns', - })) - - build.onLoad({ filter: /.*/, namespace: 'version-ns' }, () => ({ - contents: JSON.stringify({ description: version }), - loader: 'json', - })) - }, -} - -const result = await esbuild.build({ - format: 'esm', - entryPoints: [MAIN_ENTRY, CLI_ENTRY], - bundle: true, - outdir: path.join('dist', 'validator'), - minify: flags.minify, - target: ['chrome109', 'firefox109', 'safari16'], - plugins: [ - versionPlugin, - ...denoPlugins({ - configPath: path.join(dir, 'deno.json'), - }), - ], - allowOverwrite: true, - sourcemap: flags.minify ? false : 'inline', -}) - -if (result.warnings.length > 0) { - console.warn('Build reported warnings') - console.dir(result.warnings) -} - -if (result.errors.length === 0) { - Deno.exit(0) -} else { - console.error(`An issue occurred building '${MAIN_ENTRY}'`) - console.dir(result.errors) - Deno.exit(1) -} diff --git a/bids-validator/deno.json b/bids-validator/deno.json deleted file mode 100644 index e77fce327..000000000 --- a/bids-validator/deno.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@bids/validator", - "version": "1.15.0", - "exports": { - ".": "./src/bids-validator.ts", - "./main": "./src/main.ts", - "./output": "./src/utils/output.ts", - "./files": "./src/files/deno.ts", - "./options": "./src/setup/options.ts", - "./issues": "./src/issues/datasetIssues.ts" - }, - "publish": { - "exclude": [ - "bids_validator/", - "bin/", - "tests/", - "docs/", - "utils/", - "validators/", - "bids-validator-deno", - "build.ts", - "cli.js", - "esbuild.mjs", - "index.js", - "package.json", - ".npmignore", - ".gitattributes" - ] - }, - "imports": { - "@ajv": "npm:ajv@8.16.0", - "@bids/schema": "jsr:@bids/schema@0.11.4-dev.8+6e2874ce", - "@cliffy/command": "jsr:@cliffy/command@1.0.0-rc.5", - "@cliffy/table": "jsr:@cliffy/table@1.0.0-rc.5", - "@hed/validator": "npm:hed-validator@3.15.5", - "@ignore": "npm:ignore@5.3.2", - "@libs/xml": "jsr:@libs/xml@5.4.13", - "@mango/nifti": "npm:@bids/nifti-reader-js@0.6.9", - "@std/assert": "jsr:@std/assert@1.0.2", - "@std/fmt": "jsr:@std/fmt@1.0.0", - "@std/fs": "jsr:@std/fs@1.0.1", - "@std/io": "jsr:@std/io@0.224.4", - "@std/log": "jsr:@std/log@0.224.5", - "@std/path": "jsr:@std/path@1.0.2", - "@std/yaml": "jsr:@std/yaml@^1.0.4" - }, - "tasks": { - "test": "deno test -A src/tests/" - }, - "fmt": { - "lineWidth": 99, - "semiColons": false, - "singleQuote": true, - "proseWrap": "preserve", - "include": ["src/"] - } -} diff --git a/bids-validator/src/.git-meta.json b/bids-validator/src/.git-meta.json deleted file mode 100644 index 7cf172dfb..000000000 --- a/bids-validator/src/.git-meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "node": "$Format:%H$", - "date": "$Format:%cI$", - "description": "$Format:%(describe:tags=true,match=*[0-9]*)$", - "refnames": "$Format:%D$" -} diff --git a/bids-validator/src/README.md b/bids-validator/src/README.md deleted file mode 100644 index b256d5a97..000000000 --- a/bids-validator/src/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Deno based bids-validator - -## Intro - -This is a partial rewrite of the bids-validator JavaScript implementation designed to read the [bids-specification schema](https://github.com/bids-standard/bids-specification/tree/master/src/schema) to apply the majority of validation rules. - -Deno is a JavaScript and TypeScript runtime that is used to run the schema based validator. Deno is simpler than Node.js and only requires one tool to use, the Deno executable itself. To install Deno, follow these [install instructions for your platform](https://deno.land/manual/getting_started/installation). - -At the root of the repository there are two directories, `bids-validator` and `bids-validator-web`. These are separate npm packages, the Deno validator lives within the bids-validator package within the `src` directory. - -## Usage - -To use the latest validator hosted at https://deno.land/x/bids_validator, use the following command: - -```console -$ deno run --allow-read --allow-env https://deno.land/x/bids_validator/bids-validator.ts path/to/dataset -``` - -Deno by default sandboxes applications like a web browser. `--allow-read` allows the validator to read local files, and `--allow-env` enables OS-specific features. - -### Configuration file - -The schema validator accepts a JSON configuration file that reclassifies issues as -warnings, errors or ignored. - -```json -{ - "ignore": [ - { "code": "JSON_KEY_RECOMMENDED", "location": "/T1w.json" } - ], - "warning": [], - "error": [ - { "code": "NO_AUTHORS" } - ] -} -``` - -The issues are partial matches of the `issues` that the validator accumulates. -Pass the `--json` flag to see the issues in detail. - -### Development tools - -From the repository root, use `bids-validator/bids-validator-deno` to run with all permissions enabled by default: - -```shell -# Run from within the /bids-validator directory -cd bids-validator -# Run validator: -./bids-validator-deno path/to/dataset -``` - -## Schema validator test suite - -```shell -# Run tests: -deno test --allow-env --allow-read --allow-write src/ -``` - -This test suite includes running expected output from bids-examples and may throw some expected failures for bids-examples datasets where either the schema or validator are misaligned with the example dataset while under development. - -## Refreshing latest specification - -If you are validating with the latest specification instead of a specific version, the validator will hold onto a cached version. You can request the newest version by adding the `--reload` argument to obtain the newest specification definition. - -`deno run --reload=https://bids-specification.readthedocs.io/en/latest/schema.json src/main.ts` - -## Modifying and building a new schema - -To modify the schema a clone of bids-standard/bids-specification will need to be made. README and schema itself live here https://github.com/bids-standard/bids-specification/tree/master/src/schema. - -After changes to the schema have been made to a local copy the dereferenced single json file used by the validator will need to be built. The `bidsschematools` python package does this. It can be installed from pypi via pip or a local installation can be made. It lives in the specification repository here https://github.com/bids-standard/bids-specification/tree/master/tools/schemacode - -The command to compile a dereferenced schema is `bst -v export --output src/schema.json` (this assumes you are in the root of the bids-specification repo). Once compiled it can be passed to the validator via the `-s` flag, `./bids-validator-deno -s ` diff --git a/bids-validator/src/bids-validator.ts b/bids-validator/src/bids-validator.ts deleted file mode 100644 index dad420cdc..000000000 --- a/bids-validator/src/bids-validator.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { main } from './main.ts' - -const result = await main() - -const errors = result.issues.get({ severity: 'error' }) -if (errors.length) { - Deno.exit(1) -} diff --git a/bids-validator/src/files/browser.test.ts b/bids-validator/src/files/browser.test.ts deleted file mode 100644 index 7486ff4a2..000000000 --- a/bids-validator/src/files/browser.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { FileIgnoreRules } from './ignore.ts' -import { FileTree } from '../types/filetree.ts' -import { assertEquals, assertObjectMatch } from '@std/assert' -import { BIDSFileBrowser, fileListToTree } from './browser.ts' - -class TestFile extends File { - webkitRelativePath: string - constructor( - fileBits: BlobPart[], - fileName: string, - webkitRelativePath: string, - options?: FilePropertyBag | undefined, - ) { - super(fileBits, fileName, options) - this.webkitRelativePath = webkitRelativePath - } -} - -Deno.test('Browser implementation of FileTree', async (t) => { - await t.step('converts a basic FileList', async () => { - const ignore = new FileIgnoreRules([]) - const files = [ - new TestFile( - ['{}'], - 'dataset_description.json', - 'ds/dataset_description.json', - ), - new TestFile(['flat test dataset'], 'README.md', 'ds/README.md'), - ] - const tree = await fileListToTree(files) - const expectedTree = new FileTree('/', '/', undefined) - expectedTree.files = files.map((f) => { - const file = new BIDSFileBrowser(f, ignore) - file.parent = expectedTree - return file - }) - assertEquals(tree, expectedTree) - }) - await t.step('converts a simple FileList with several levels', async () => { - const ignore = new FileIgnoreRules([]) - const files = [ - new TestFile( - ['{}'], - 'dataset_description.json', - 'ds/dataset_description.json', - ), - new TestFile( - ['tsv headers\n', 'column\tdata'], - 'participants.tsv', - 'ds/participants.tsv', - ), - new TestFile( - ['single subject test dataset'], - 'README.md', - 'ds/README.md', - ), - new TestFile( - ['nifti file goes here'], - 'sub-01_T1w.nii.gz', - 'ds/sub-01/anat/sub-01_T1w.nii.gz', - ), - ] - const tree = await fileListToTree(files) - const expectedTree = new FileTree('/', '/', undefined) - const sub01Tree = new FileTree('/sub-01', 'sub-01', expectedTree) - const anatTree = new FileTree('/sub-01/anat', 'anat', sub01Tree) - expectedTree.files = files - .slice(0, 3) - .map((f) => { - const file = new BIDSFileBrowser(f, ignore) - file.parent = expectedTree - return file - }) - expectedTree.directories.push(sub01Tree) - anatTree.files = [new BIDSFileBrowser(files[3], ignore)] - anatTree.files[0].parent = anatTree - sub01Tree.directories.push(anatTree) - assertEquals(tree, expectedTree) - }) - - await t.step('reads .bidsignore during load', async () => { - const ignore = new FileIgnoreRules([]) - const files = [ - new TestFile( - ['ignored_but_absent\n', 'ignored_and_present\n'], - '.bidsignore', - 'ds/.bidsignore', - ), - new TestFile( - ['{}'], - 'dataset_description.json', - 'ds/dataset_description.json', - ), - new TestFile( - ['tsv headers\n', 'column\tdata'], - 'participants.tsv', - 'ds/participants.tsv', - ), - new TestFile( - ['single subject test dataset'], - 'README.md', - 'ds/README.md', - ), - new TestFile( - ['Anything can be in an ignored file'], - 'ignored_and_present', - 'ds/ignored_and_present', - ), - new TestFile( - ['nifti file goes here'], - 'sub-01_T1w.nii.gz', - 'ds/sub-01/anat/sub-01_T1w.nii.gz', - ), - ] - const tree = await fileListToTree(files) - const expectedTree = new FileTree('/', '/', undefined) - const sub01Tree = new FileTree('/sub-01', 'sub-01', expectedTree) - const anatTree = new FileTree('/sub-01/anat', 'anat', sub01Tree) - expectedTree.files = files - .slice(0, 5) - .map((f) => { - const file = new BIDSFileBrowser(f, ignore) - file.parent = expectedTree - return file - }) - expectedTree.directories.push(sub01Tree) - anatTree.files = [new BIDSFileBrowser(files[5], ignore)] - anatTree.files[0].parent = anatTree - sub01Tree.directories.push(anatTree) - assertEquals(tree, expectedTree) - - assertEquals(tree.get('ignored_and_present')?.ignored, true) - }) -}) - -Deno.test('Spread copies of BIDSFileBrowser contain name and path properties', async () => { - const ignore = new FileIgnoreRules([]) - const files = [ - new TestFile( - ['{}'], - 'dataset_description.json', - 'ds/dataset_description.json', - ), - new TestFile(['flat test dataset'], 'README.md', 'ds/README.md'), - ] - const tree = await fileListToTree(files) - const expectedTree = new FileTree('/', '/', undefined) - expectedTree.files = files.map((f) => { - const file = new BIDSFileBrowser(f, ignore) - file.parent = expectedTree - return file - }) - assertEquals(tree, expectedTree) - const spreadFile = { ...expectedTree.files[0], evidence: 'test evidence' } - assertObjectMatch(spreadFile, { - name: 'dataset_description.json', - path: '/dataset_description.json', - evidence: 'test evidence', - }) -}) diff --git a/bids-validator/src/files/browser.ts b/bids-validator/src/files/browser.ts deleted file mode 100644 index 4a52b1dcf..000000000 --- a/bids-validator/src/files/browser.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { type BIDSFile, FileTree } from '../types/filetree.ts' -import { filesToTree } from './filetree.ts' -import { FileIgnoreRules, readBidsIgnore } from './ignore.ts' -import { parse, SEPARATOR_PATTERN } from '@std/path' -import * as posix from '@std/path/posix' - -/** - * Browser implement of BIDSFile wrapping native File/FileList types - */ -export class BIDSFileBrowser implements BIDSFile { - #ignore: FileIgnoreRules - #file: File - name: string - path: string - parent: FileTree - viewed: boolean = false - - constructor(file: File, ignore: FileIgnoreRules, parent?: FileTree) { - this.#file = file - this.#ignore = ignore - this.name = file.name - const relativePath = this.#file.webkitRelativePath - const prefixLength = relativePath.indexOf('/') - this.path = relativePath.substring(prefixLength) - this.parent = parent ?? new FileTree('', '/', undefined) - } - - get size(): number { - return this.#file.size - } - - get stream(): ReadableStream { - return this.#file.stream() - } - - get ignored(): boolean { - return this.#ignore.test(this.path) - } - - text(): Promise { - return this.#file.text() - } - - async readBytes(size: number, offset = 0): Promise { - return new Uint8Array(await this.#file.slice(offset, size).arrayBuffer()) - } -} - -/** - * Convert from FileList (created with webkitDirectory: true) to FileTree for validator use - */ -export async function fileListToTree(files: File[]): Promise { - const ignore = new FileIgnoreRules([]) - const root = new FileTree('/', '/', undefined) - const tree = filesToTree(files.map((f) => new BIDSFileBrowser(f, ignore, root))) - const bidsignore = tree.get('.bidsignore') - if (bidsignore) { - try { - ignore.add(await readBidsIgnore(bidsignore as BIDSFile)) - } catch (err) { - console.log(`Failed to read '.bidsignore' file with the following error:\n${err}`) - } - } - return tree -} diff --git a/bids-validator/src/files/deno.test.ts b/bids-validator/src/files/deno.test.ts deleted file mode 100644 index 24e4c13f3..000000000 --- a/bids-validator/src/files/deno.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { assert, assertEquals, assertRejects } from '@std/assert' -import { readAll, readerFromStreamReader } from '@std/io' -import { basename, dirname, fromFileUrl, join } from '@std/path' -import { EOL } from '@std/fs' -import { FileTree } from '../types/filetree.ts' -import { BIDSFileDeno, readFileTree, UnicodeDecodeError } from './deno.ts' -import { requestReadPermission } from '../setup/requestPermissions.ts' -import { FileIgnoreRules } from './ignore.ts' - -await requestReadPermission() - -// Use this file for testing file behavior -const testUrl = import.meta.url -const testPath = fromFileUrl(testUrl) -const testDir = dirname(testPath) -const testFilename = basename(testPath) -const ignore = new FileIgnoreRules([]) - -Deno.test('Deno implementation of BIDSFile', async (t) => { - await t.step('implements basic file properties', () => { - const file = new BIDSFileDeno(testDir, testFilename, ignore) - assertEquals(join(testDir, file.path), testPath) - }) - await t.step('implements correct file size', async () => { - const { size } = await Deno.stat(testPath) - const file = new BIDSFileDeno(testDir, testFilename, ignore) - assertEquals(await file.size, size) - }) - await t.step('can be read as ReadableStream', async () => { - const file = new BIDSFileDeno(testDir, testFilename, ignore) - const stream = file.stream - const streamReader = stream.getReader() - const denoReader = readerFromStreamReader(streamReader) - const fileBuffer = await readAll(denoReader) - assertEquals(await file.size, fileBuffer.length) - }) - await t.step('can be read with .text() method', async () => { - const file = new BIDSFileDeno(testDir, testFilename, ignore) - const text = await file.text() - assertEquals(await file.size, text.length) - }) - await t.step( - 'throws UnicodeDecodeError when reading a UTF-16 file with text() method', - async () => { - // BOM is invalid in JSON but shows up often from certain tools, so abstract handling it - const bomDir = join(testPath, '..', '..', 'tests') - const bomFilename = 'bom-utf16.tsv' - const file = new BIDSFileDeno(bomDir, bomFilename, ignore) - await assertRejects(async () => file.text(), UnicodeDecodeError) - }, - ) - await t.step( - 'strips BOM characters when reading UTF-8 via .text()', - async () => { - // BOM is invalid in JSON but shows up often from certain tools, so abstract handling it - const bomDir = join(testPath, '..', '..', 'tests') - const bomFilename = 'bom-utf8.json' - const file = new BIDSFileDeno(bomDir, bomFilename, ignore) - const text = await file.text() - assertEquals(text, ['{', ' "example": "JSON for test suite"', '}', ''].join(EOL)) - }, - ) -}) - -Deno.test('Deno implementation of FileTree', async (t) => { - const srcdir = dirname(testDir) - const parent = basename(testDir) - const tree = await readFileTree(srcdir) - await t.step('uses POSIX relative paths', async () => { - assertEquals(tree.path, '/') - const parentObj = tree.get(parent) as FileTree - assert(parentObj !== undefined) - assertEquals(parentObj.path, `/${parent}`) - const testObj = parentObj.get(testFilename) as BIDSFileDeno - assert(testObj !== undefined) - assertEquals(testObj.path, `/${parent}/${testFilename}`) - }) -}) diff --git a/bids-validator/src/files/deno.ts b/bids-validator/src/files/deno.ts deleted file mode 100644 index 2d2c7d373..000000000 --- a/bids-validator/src/files/deno.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Deno specific implementation for reading files - */ -import { basename, join } from '@std/path' -import * as posix from '@std/path/posix' -import { type BIDSFile, FileTree } from '../types/filetree.ts' -import { requestReadPermission } from '../setup/requestPermissions.ts' -import { FileIgnoreRules, readBidsIgnore } from './ignore.ts' -import { logger } from '../utils/logger.ts' -export { type BIDSFile, FileTree } - -/** - * Thrown when a text file is decoded as UTF-8 but contains UTF-16 characters - */ -export class UnicodeDecodeError extends Error { - constructor(message: string) { - super(message) - this.name = 'UnicodeDecode' - } -} - -/** - * Deno implementation of BIDSFile - */ -export class BIDSFileDeno implements BIDSFile { - #ignore: FileIgnoreRules - name: string - path: string - parent: FileTree - #fileInfo?: Deno.FileInfo - #datasetAbsPath: string - viewed: boolean = false - - constructor(datasetPath: string, path: string, ignore?: FileIgnoreRules, parent?: FileTree) { - this.#datasetAbsPath = datasetPath - this.path = path - this.name = basename(path) - this.#ignore = ignore ?? new FileIgnoreRules([]) - try { - this.#fileInfo = Deno.statSync(this._getPath()) - } catch (error) { - if (error.code === 'ENOENT') { - this.#fileInfo = Deno.lstatSync(this._getPath()) - } - } - this.parent = parent ?? new FileTree('', '/', undefined) - } - - private _getPath(): string { - return join(this.#datasetAbsPath, this.path) - } - - get size(): number { - return this.#fileInfo ? this.#fileInfo.size : -1 - } - - get stream(): ReadableStream { - const handle = this.#openHandle() - return handle.readable - } - - get ignored(): boolean { - return this.#ignore.test(this.path) - } - - /** - * Read the entire file and decode as utf-8 text - */ - async text(): Promise { - const streamReader = this.stream - .pipeThrough(new TextDecoderStream('utf-8')) - .getReader() - let data = '' - try { - // Read once to check for unicode issues - const { done, value } = await streamReader.read() - // Check for UTF-16 BOM - if (value && value.startsWith('\uFFFD')) { - throw new UnicodeDecodeError('This file appears to be UTF-16') - } - if (done) return data - data += value - // Continue reading the rest of the file if no unicode issues were found - while (true) { - const { done, value } = await streamReader.read() - if (done) return data - data += value - } - } finally { - streamReader.releaseLock() - } - } - - /** - * Read bytes in a range efficiently from a given file - */ - async readBytes(size: number, offset = 0): Promise { - const handle = this.#openHandle() - const buf = new Uint8Array(size) - await handle.seek(offset, Deno.SeekMode.Start) - await handle.read(buf) - handle.close() - return buf - } - - /** - * Return a Deno file handle - */ - #openHandle(): Deno.FsFile { - // Avoid asking for write access - const openOptions = { read: true, write: false } - return Deno.openSync(this._getPath(), openOptions) - } -} - -async function _readFileTree( - rootPath: string, - relativePath: string, - ignore: FileIgnoreRules, - parent?: FileTree, -): Promise { - await requestReadPermission() - const name = basename(relativePath) - const tree = new FileTree(relativePath, name, parent, ignore) - - for await (const dirEntry of Deno.readDir(join(rootPath, relativePath))) { - if (dirEntry.isFile || dirEntry.isSymlink) { - const file = new BIDSFileDeno( - rootPath, - posix.join(relativePath, dirEntry.name), - ignore, - ) - file.parent = tree - tree.files.push(file) - } - if (dirEntry.isDirectory) { - const dirTree = await _readFileTree( - rootPath, - posix.join(relativePath, dirEntry.name), - ignore, - tree, - ) - tree.directories.push(dirTree) - } - } - return tree -} - -/** - * Read in the target directory structure and return a FileTree - */ -export async function readFileTree(rootPath: string): Promise { - const ignore = new FileIgnoreRules([]) - try { - const ignoreFile = new BIDSFileDeno( - rootPath, - '.bidsignore', - ignore, - ) - ignore.add(await readBidsIgnore(ignoreFile)) - } catch (err) { - if (!Object.hasOwn(err, 'code') || err.code !== 'ENOENT') { - logger.error(`Failed to read '.bidsignore' file with the following error:\n${err}`) - } - } - return _readFileTree(rootPath, '/', ignore) -} diff --git a/bids-validator/src/files/dwi.ts b/bids-validator/src/files/dwi.ts deleted file mode 100644 index b7465b62a..000000000 --- a/bids-validator/src/files/dwi.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * DWI - * Module for parsing DWI-associated files - */ -const normalizeEOL = (str: string): string => str.replace(/\r\n/g, '\n').replace(/\r/g, '\n') - -export function parseBvalBvec(contents: string): string[][] { - // BVEC files are a matrix of numbers, with each row being - // a different axis - // BVAL files are a single row of numbers, and may contain - // trailing whitespace - return normalizeEOL(contents) - .split(/\s*\n/) // Split on newlines, ignoring trailing whitespace - .filter((x) => x.match(/\S/)) // Remove empty lines - .map((row) => row.split(/\s+/)) -} diff --git a/bids-validator/src/files/filetree.test.ts b/bids-validator/src/files/filetree.test.ts deleted file mode 100644 index 9c990bb1e..000000000 --- a/bids-validator/src/files/filetree.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { assert, assertEquals } from '@std/assert' -import { FileIgnoreRules } from './ignore.ts' -import { FileTree } from '../types/filetree.ts' -import { filesToTree, pathsToTree } from './filetree.ts' - -Deno.test('FileTree generation', async (t) => { - await t.step('converts a basic list', async () => { - const tree = pathsToTree(['/dataset_description.json', '/README.md']) - assertEquals(tree.directories, []) - assertEquals(tree.files.map((f) => f.name), ['dataset_description.json', 'README.md']) - }) - - await t.step('converts a simple list with several levels', async () => { - const tree = pathsToTree([ - '/dataset_description.json', - '/participants.tsv', - '/README.md', - '/sub-01/anat/sub-01_T1w.nii.gz', - ]) - - assertEquals(tree.directories.map((d) => d.name), ['sub-01']) - - const sub01 = tree.directories[0] - assertEquals(sub01.path, '/sub-01') - assertEquals(sub01.parent, tree) - assertEquals(sub01.directories.map((d) => d.name), ['anat']) - - const anat = sub01.directories[0] - assertEquals(anat.path, '/sub-01/anat') - assertEquals(anat.parent, sub01) - assertEquals(anat.files.map((f) => f.name), ['sub-01_T1w.nii.gz']) - - const t1w = anat.files[0] - assertEquals(t1w.path, '/sub-01/anat/sub-01_T1w.nii.gz') - assertEquals(t1w.parent, anat) - - assertEquals(tree.files.map((f) => f.name), [ - 'dataset_description.json', - 'participants.tsv', - 'README.md', - ]) - assertEquals(tree.files.map((f) => f.path), [ - '/dataset_description.json', - '/participants.tsv', - '/README.md', - ]) - assertEquals(tree.files.map((f) => f.parent), [tree, tree, tree]) - - assert(tree.contains(['sub-01'])) - assert(tree.contains(['sub-01', 'anat'])) - assert(tree.contains(['sub-01', 'anat', 'sub-01_T1w.nii.gz'])) - }) -}) diff --git a/bids-validator/src/files/filetree.ts b/bids-validator/src/files/filetree.ts deleted file mode 100644 index aac9734e8..000000000 --- a/bids-validator/src/files/filetree.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { parse, SEPARATOR_PATTERN } from '@std/path' -import * as posix from '@std/path/posix' -import { BIDSFile, FileTree } from '../types/filetree.ts' - -const nullFile = { - size: 0, - ignored: false, - stream: new ReadableStream(), - text: () => Promise.resolve(''), - readBytes: async (size: number, offset?: number) => new Uint8Array(), - parent: new FileTree('', '/'), - viewed: false, -} - -export function pathToFile(path: string): BIDSFile { - const name = path.split('/').pop() as string - return { name, path, ...nullFile } -} - -export function pathsToTree(paths: string[]): FileTree { - return filesToTree(paths.map(pathToFile)) -} - -export function filesToTree(fileList: BIDSFile[]): FileTree { - const tree: FileTree = new FileTree('/', '/') - for (const file of fileList) { - const parts = parse(file.path) - if (parts.dir === '/') { - tree.files.push(file) - file.parent = tree - continue - } - let current = tree - for (const level of parts.dir.split(SEPARATOR_PATTERN).slice(1)) { - const exists = current.get(level) as FileTree - if (exists) { - current = exists - continue - } - const newTree = new FileTree(posix.join(current.path, level), level, current) - current.directories.push(newTree) - current = newTree - } - current.files.push(file) - file.parent = current - } - return tree -} diff --git a/bids-validator/src/files/gzip.test.ts b/bids-validator/src/files/gzip.test.ts deleted file mode 100644 index c3f9f5532..000000000 --- a/bids-validator/src/files/gzip.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { assert, assertObjectMatch } from '@std/assert' -import { parseGzip } from './gzip.ts' -import { BIDSFileDeno } from './deno.ts' - -Deno.test('parseGzip', async (t) => { - await t.step('parses anonymized file', async () => { - const file = new BIDSFileDeno('tests/data/gzip', 'anon.gz') - const gzip = await parseGzip(file) - assert(gzip) - assertObjectMatch(gzip, { - timestamp: 0, - filename: '', - }) - }) - await t.step('parses unanonymized file', async () => { - const file = new BIDSFileDeno('tests/data/gzip', 'stamped.gz') - const gzip = await parseGzip(file) - assert(gzip) - assertObjectMatch(gzip, { - timestamp: 0xb1d5cafe, - filename: 'stamped', - }) - }) - await t.step('parses commented file', async () => { - const file = new BIDSFileDeno('tests/data/gzip', 'commented.gz') - const gzip = await parseGzip(file) - assert(gzip) - assertObjectMatch(gzip, { - timestamp: 0xb1d5cafe, - filename: 'stamped', - comment: 'comment', - }) - }) - await t.step('gracefully handles empty file', async () => { - const file = new BIDSFileDeno( - 'tests/data/bids-examples/7t_trt', - 'sub-01/ses-1/anat/sub-01_ses-1_T1map.nii.gz', - ) - const gzip = await parseGzip(file) - assert(!gzip) - }) -}) diff --git a/bids-validator/src/files/gzip.ts b/bids-validator/src/files/gzip.ts deleted file mode 100644 index e15f0f85c..000000000 --- a/bids-validator/src/files/gzip.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * GZIP - * Module for extracting gzip metadata from a file - */ -import { Gzip } from '@bids/schema/context' -import { BIDSFile } from '../types/filetree.ts' - -/** - * Parse a gzip header from a file - * - * Extracts the timestamp, filename, and comment from a gzip file, - * sufficient to determine if there may be non-obvious leakage of - * sensitive information. - * - * @param file - The file to parse - * @param maxBytes - The maximum number of bytes to read - */ -export async function parseGzip( - file: BIDSFile, - maxBytes: number = 512, -): Promise { - const buf = await file.readBytes(maxBytes) - const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength) - if (view.byteLength < 2 || view.getUint16(0, false) !== 0x1f8b) return undefined - - const flags = buf[3] - const hasExtra = flags & 0x04 // FEXTRA - const hasFilename = flags & 0x08 // FNAME - const hasComment = flags & 0x10 // FCOMMENT - - const timestamp = view.getUint32(4, true) - - let offset = 10 - if (hasExtra) { - const xlen = view.getUint16(10, true) - offset += 2 + xlen - } - - let filename = '' - if (hasFilename) { - const end = buf.indexOf(0, offset) - filename = new TextDecoder().decode(buf.subarray(offset, end)) - offset = end + 1 - } - - let comment = '' - if (hasComment) { - const end = buf.indexOf(0, offset) - comment = new TextDecoder().decode(buf.subarray(offset, end)) - } - - return { timestamp, filename, comment } -} diff --git a/bids-validator/src/files/ignore.test.ts b/bids-validator/src/files/ignore.test.ts deleted file mode 100644 index a8f15a383..000000000 --- a/bids-validator/src/files/ignore.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { assertEquals } from '@std/assert' -import { FileIgnoreRules } from './ignore.ts' - -Deno.test('Deno implementation of FileIgnoreRules', async (t) => { - await t.step('handles basic .bidsignore rules', () => { - const files = [ - '/sub-01/anat/sub-01_T1w.nii.gz', - '/dataset_description.json', - '/README', - '/CHANGES', - '/participants.tsv', - '/.git/HEAD', - '/.datalad/config', - '/sub-01/anat/non-bidsy-file.xyz', - '/explicit/full/path.nii', - ] - const rules = ['.git', '**/*.xyz', 'explicit/full/path.nii'] - const ignore = new FileIgnoreRules(rules) - const filtered = files.filter((path) => !ignore.test(path)) - assertEquals(filtered, [ - '/sub-01/anat/sub-01_T1w.nii.gz', - '/dataset_description.json', - '/README', - '/CHANGES', - '/participants.tsv', - ]) - }) -}) diff --git a/bids-validator/src/files/ignore.ts b/bids-validator/src/files/ignore.ts deleted file mode 100644 index 6cb1cb358..000000000 --- a/bids-validator/src/files/ignore.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { BIDSFile } from '../types/filetree.ts' -import { default as ignore } from '@ignore' -import type { Ignore } from '@ignore' - -export async function readBidsIgnore(file: BIDSFile) { - const value = await file.text() - if (value) { - const lines = value.split('\n') - return lines - } else { - return [] - } -} - -const defaultIgnores = [ - '.git**', - '.*', - 'sourcedata/', - 'code/', - 'stimuli/', - 'log/', -] - -/** - * Deno implementation of .bidsignore style rules - */ -export class FileIgnoreRules { - #ignore: Ignore - - constructor(config: string[]) { - // @ts-expect-error - this.#ignore = ignore() - this.#ignore.add(defaultIgnores) - this.#ignore.add(config) - } - - add(config: string[]): void { - this.#ignore.add(config) - } - - /** Test if a dataset relative path should be ignored given configured rules */ - test(path: string): boolean { - // Paths come in with a leading slash, but ignore expects paths relative to root - return this.#ignore.ignores(path.slice(1, path.length)) - } -} diff --git a/bids-validator/src/files/inheritance.test.ts b/bids-validator/src/files/inheritance.test.ts deleted file mode 100644 index 1d84c6a8b..000000000 --- a/bids-validator/src/files/inheritance.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { assertEquals, assertThrows } from '@std/assert' -import { pathsToTree } from './filetree.ts' -import { walkBack } from './inheritance.ts' - -Deno.test('walkback inheritance tests', async (t) => { - await t.step('walkBack throws multiple inheritance error', async () => { - const rootFileTree = pathsToTree([ - '/T1w.json', - '/acq-MPRAGE_T1w.json', - '/sub-01/anat/sub-01_acq-MPRAGE_T1w.nii.gz', - ]) - const dataFile = rootFileTree.directories[0].directories[0].files[0] - assertThrows(() => { - try { - const sidecars = walkBack(dataFile) - for (const f of sidecars) { - continue - } - } catch (error) { - assertEquals(error.code, 'MULTIPLE_INHERITABLE_FILES') - throw error - } - }) - }) - await t.step( - 'no error thrown on exact inheritance match with multiple valid candidates', - async () => { - const rootFileTree = pathsToTree([ - '/T1w.json', - '/sub-01/anat/sub-01_acq-MPRAGE_T1w.nii.gz', - '/sub-01/anat/sub-01_acq-MPRAGE_T1w.json', - '/sub-01/anat/sub-01_T1w.json', - ]) - const dataFile = rootFileTree.directories[0].directories[0].files[0] - const sidecars = walkBack(dataFile) - const sidecarFiles = [] - for (const f of sidecars) { - sidecarFiles.push(f) - } - assertEquals(sidecarFiles.length, 2) - assertEquals(sidecarFiles.map((f) => f.path), [ - '/sub-01/anat/sub-01_acq-MPRAGE_T1w.json', - '/T1w.json', - ]) - }, - ) -}) diff --git a/bids-validator/src/files/inheritance.ts b/bids-validator/src/files/inheritance.ts deleted file mode 100644 index 97673a322..000000000 --- a/bids-validator/src/files/inheritance.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { BIDSFile, FileTree } from '../types/filetree.ts' -import type { Context } from '@bids/schema' -import { readEntities } from '../schema/entities.ts' - -export function* walkBack( - source: BIDSFile, - inherit: boolean = true, - targetExtensions: string[] = ['.json'], - targetSuffix?: string, -): Generator { - const sourceParts = readEntities(source.name) - - targetSuffix = targetSuffix || sourceParts.suffix - - let fileTree: FileTree | undefined = source.parent - while (fileTree) { - const candidates = fileTree.files.filter((file) => { - const { suffix, extension, entities } = readEntities(file.name) - return ( - targetExtensions.includes(extension) && - suffix === targetSuffix && - Object.keys(entities).every((entity) => entities[entity] === sourceParts.entities[entity]) - ) - }) - if (candidates.length > 1) { - const exactMatch = candidates.find((file) => { - const { suffix, extension, entities } = readEntities(file.name) - return Object.keys(sourceParts.entities).every((entity) => - entities[entity] === sourceParts.entities[entity] - ) - }) - if (exactMatch) { - exactMatch.viewed = true - yield exactMatch - } else { - const paths = candidates.map((x) => x.path).sort() - throw { - code: 'MULTIPLE_INHERITABLE_FILES', - location: paths[0], - affects: source.path, - issueMessage: `Candidate files: ${paths}`, - } - } - } else if (candidates.length === 1) { - candidates[0].viewed = true - yield candidates[0] - } - if (!inherit) break - fileTree = fileTree.parent - } -} diff --git a/bids-validator/src/files/json.test.ts b/bids-validator/src/files/json.test.ts deleted file mode 100644 index 090568460..000000000 --- a/bids-validator/src/files/json.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type assert, assertObjectMatch } from '@std/assert' -import type { BIDSFileDeno, UnicodeDecodeError } from './deno.ts' -import type { BIDSFile } from '../types/filetree.ts' -import type { FileIgnoreRules } from './ignore.ts' - -import { loadJSON } from './json.ts' - -function encodeUTF16(text: string) { - // Adapted from https://developer.chrome.com/blog/how-to-convert-arraybuffer-to-and-from-string - - // Ensure BOM is present - text = text.charCodeAt(0) === 0xFEFF ? text : '\uFEFF' + text - const buffer = new ArrayBuffer(text.length * 2) - const view = new Uint16Array(buffer) - for (let i = 0, strLen = text.length; i < strLen; i++) { - view[i] = text.charCodeAt(i) - } - return buffer -} - -function makeFile(text: string, encoding: string): BIDSFile { - const bytes = encoding === 'utf-8' ? new TextEncoder().encode(text) : encodeUTF16(text) - return { - readBytes: async (size: number) => { - return new Uint8Array(bytes) - }, - size: bytes.byteLength, - } as unknown as BIDSFile -} - -Deno.test('Test JSON error conditions', async (t) => { - await t.step('Load valid JSON', async () => { - const JSONfile = makeFile('{"a": 1}', 'utf-8') - const result = await loadJSON(JSONfile) - assertObjectMatch(result, { a: 1 }) - }) - - await t.step('Error on BOM', async () => { - const BOMfile = makeFile('\uFEFF{"a": 1}', 'utf-8') - let error: any = undefined - await loadJSON(BOMfile).catch((e) => { - error = e - }) - assertObjectMatch(error, { key: 'INVALID_JSON_ENCODING' }) - }) - - await t.step('Error on UTF-16', async () => { - const UTF16file = makeFile('{"a": 1}', 'utf-16') - let error: any = undefined - await loadJSON(UTF16file).catch((e) => { - error = e - }) - assertObjectMatch(error, { key: 'INVALID_JSON_ENCODING' }) - }) - - await t.step('Error on invalid JSON syntax', async () => { - const badJSON = makeFile('{"a": 1]', 'utf-8') - let error: any = undefined - await loadJSON(badJSON).catch((e) => { - error = e - }) - assertObjectMatch(error, { key: 'JSON_INVALID' }) - }) -}) diff --git a/bids-validator/src/files/json.ts b/bids-validator/src/files/json.ts deleted file mode 100644 index b38b48b46..000000000 --- a/bids-validator/src/files/json.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { BIDSFile } from '../types/filetree.ts' - -async function readJSONText(file: BIDSFile): Promise { - // Read JSON text from a file - // JSON must be encoded in UTF-8 without a byte order mark (BOM) - const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }) - // Streaming TextDecoders are buggy in Deno and Chrome, so read the - // entire file into memory before decoding and parsing - const data = await file.readBytes(file.size) - try { - const text = decoder.decode(data) - if (text.startsWith('\uFEFF')) { - throw {} - } - return text - } catch (error) { - throw { key: 'INVALID_JSON_ENCODING' } - } finally { - decoder.decode() // Reset decoder - } -} - -export async function loadJSON(file: BIDSFile): Promise> { - const text = await readJSONText(file) // Raise encoding errors - try { - return JSON.parse(text) - } catch (error) { - throw { key: 'JSON_INVALID' } // Raise syntax errors - } -} diff --git a/bids-validator/src/files/nifti.test.ts b/bids-validator/src/files/nifti.test.ts deleted file mode 100644 index 909c49b56..000000000 --- a/bids-validator/src/files/nifti.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { assert, assertObjectMatch } from '@std/assert' -import { FileIgnoreRules } from './ignore.ts' -import { BIDSFileDeno } from './deno.ts' - -import { loadHeader } from './nifti.ts' - -Deno.test('Test loading nifti header', async (t) => { - const ignore = new FileIgnoreRules([]) - - await t.step('Load header from compressed 3D file', async () => { - const path = 'sub-01/anat/sub-01_T1w.nii.gz' - const root = './tests/data/valid_headers' - const file = new BIDSFileDeno(root, path, ignore) - const header = await loadHeader(file) - assert(header !== undefined) - assertObjectMatch(header, { - dim: [3, 40, 48, 48, 1, 1, 1, 1], - shape: [40, 48, 48], - dim_info: { freq: 0, phase: 0, slice: 0 }, - xyzt_units: { xyz: 'mm', t: 'sec' }, - qform_code: 1, - sform_code: 1, - }) - // Annoying floating point precision, skipping pixdim details - assert(header.voxel_sizes.length === 3) - }) - - await t.step('Load header from compressed 4D file', async () => { - const path = 'sub-01/func/sub-01_task-rhymejudgment_bold.nii.gz' - const root = './tests/data/valid_headers' - const file = new BIDSFileDeno(root, path, ignore) - const header = await loadHeader(file) - assert(header !== undefined) - assertObjectMatch(header, { - dim: [4, 16, 16, 9, 20, 1, 1, 1], - pixdim: [0, 12.5, 12.5, 16, 1, 0, 0, 0], - shape: [16, 16, 9, 20], - voxel_sizes: [12.5, 12.5, 16, 1], - dim_info: { freq: 0, phase: 0, slice: 0 }, - xyzt_units: { xyz: 'mm', t: 'sec' }, - qform_code: 0, - sform_code: 0, - }) - }) - - await t.step('Fail on non-nifti file', async () => { - const path = 'sub-01/func/sub-01_task-rhymejudgment_events.tsv' - const root = './tests/data/valid_headers' - const file = new BIDSFileDeno(root, path, ignore) - let error: any = undefined - const header = await loadHeader(file).catch((e) => { - error = e - }) - assertObjectMatch(error, { key: 'NIFTI_HEADER_UNREADABLE' }) - }) -}) diff --git a/bids-validator/src/files/nifti.ts b/bids-validator/src/files/nifti.ts deleted file mode 100644 index 58a058958..000000000 --- a/bids-validator/src/files/nifti.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { isCompressed, readHeader } from '@mango/nifti' -import type { BIDSFile } from '../types/filetree.ts' -import { logger } from '../utils/logger.ts' -import type { NiftiHeader } from '@bids/schema/context' - -async function extract(buffer: Uint8Array, nbytes: number): Promise { - // The fflate decompression that is used in nifti-reader does not like - // truncated data, so pretend that we have a stream and stop reading - // when we have enough bytes. - const result = new Uint8Array(nbytes) - const stream = new ReadableStream({ - start(controller) { - controller.enqueue(buffer) - }, - }) - const reader = stream.pipeThrough(new DecompressionStream('gzip')).getReader() - let offset = 0 - while (offset < nbytes) { - const { value, done } = await reader.read() - if (done) { - break - } - result.set(value.subarray(0, Math.min(value.length, nbytes - offset)), offset) - offset += value.length - } - await reader.cancel() - return result -} - -export async function loadHeader(file: BIDSFile): Promise { - try { - const buf = await file.readBytes(1024) - const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf - const header = readHeader(data.buffer) - if (!header) { - throw { key: 'NIFTI_HEADER_UNREADABLE' } - } - const ndim = header.dims[0] - return { - dim: header.dims, - // Hack: round pixdim to 3 decimal places; schema should add rounding function - pixdim: header.pixDims.map((pixdim) => Math.round(pixdim * 1000) / 1000), - shape: header.dims.slice(1, ndim + 1), - voxel_sizes: header.pixDims.slice(1, ndim + 1), - dim_info: { - freq: header.dim_info & 0x03, - phase: (header.dim_info >> 2) & 0x03, - slice: (header.dim_info >> 4) & 0x03, - }, - xyzt_units: { - xyz: ['unknown', 'meter', 'mm', 'um'][header.xyzt_units & 0x03], - t: ['unknown', 'sec', 'msec', 'usec'][(header.xyzt_units >> 3) & 0x03], - }, - qform_code: header.qform_code, - sform_code: header.sform_code, - } as NiftiHeader - } catch (err) { - throw { key: 'NIFTI_HEADER_UNREADABLE' } - } -} diff --git a/bids-validator/src/files/tiff.test.ts b/bids-validator/src/files/tiff.test.ts deleted file mode 100644 index 8376122b1..000000000 --- a/bids-validator/src/files/tiff.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { assert, assertObjectMatch } from '@std/assert' -import { parseTIFF } from './tiff.ts' -import { BIDSFileDeno } from './deno.ts' - -Deno.test('parseTIFF', async (t) => { - await t.step('parse example file as TIFF', async () => { - const file = new BIDSFileDeno( - 'tests/data/bids-examples/micr_SPIM', - 'sub-01/micr/sub-01_sample-A_stain-LFB_chunk-01_SPIM.ome.tif', - ) - const { tiff, ome } = await parseTIFF(file, false) - assert(tiff) - assert(!ome) - assertObjectMatch(tiff, { - version: 42, - }) - }) - await t.step('parse example file as OME-TIFF', async () => { - const file = new BIDSFileDeno( - 'tests/data/bids-examples/micr_SPIM', - 'sub-01/micr/sub-01_sample-A_stain-LFB_chunk-01_SPIM.ome.tif', - ) - const { tiff, ome } = await parseTIFF(file, true) - assert(tiff) - assert(ome) - assertObjectMatch(tiff, { - version: 42, - }) - assertObjectMatch(ome, { - PhysicalSizeX: 1, - PhysicalSizeY: 1, - PhysicalSizeZ: 1, - PhysicalSizeXUnit: 'µm', - PhysicalSizeYUnit: 'µm', - PhysicalSizeZUnit: 'µm', - }) - }) - await t.step('parse OME+BigTIFF file', async () => { - const file = new BIDSFileDeno('tests/data/ome-tiff', 'btif_id.ome.tif') - const { tiff, ome } = await parseTIFF(file, true) - assert(tiff) - assert(ome) - assertObjectMatch(tiff, { - version: 43, - }) - assertObjectMatch(ome, { - PhysicalSizeX: 1, - PhysicalSizeY: 1, - PhysicalSizeZ: 1, - PhysicalSizeXUnit: 'µm', - PhysicalSizeYUnit: 'µm', - PhysicalSizeZUnit: 'µm', - }) - }) -}) diff --git a/bids-validator/src/files/tiff.ts b/bids-validator/src/files/tiff.ts deleted file mode 100644 index ba828564a..000000000 --- a/bids-validator/src/files/tiff.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * TIFF - * Module for extracting Tiff metadata - */ -import { Ome, Tiff } from '@bids/schema/context' -import * as XML from '@libs/xml' -import { BIDSFile } from '../types/filetree.ts' - -function getImageDescription( - dataview: DataView, - littleEndian: boolean, - IFDsize: number, -): string | undefined { - // Custom implementation based off of - // https://www.alternatiff.com/resources/TIFF6.pdf - // https://www.loc.gov/preservation/digital/formats/fdd/fdd000328.shtml - // - // The TIFF header is 8 bytes long, and the second 4 bytes are the offset to the IFD list - // The IFD list starts with a 2 byte count of the number of IFDs, followed by offsets to each IFD - // Each IFD is 12 (or 20) bytes long, and the first 2 bytes are the tag, bytes 4-8 are the count - // of values, and bytes 8-12 are the value or the offset to the values - // - // The ImageDescription tag is 0x010e, and the value is a UTF-8 string - const IFDoffset = dataview.getUint32(4, littleEndian) - const IFDcount = dataview.getUint16(IFDoffset, littleEndian) - for (let i = 0; i < IFDcount; i++) { - if (dataview.getUint16(IFDoffset + 2 + i * IFDsize, littleEndian) === 0x010e) { - const nbytes = dataview.getUint32(IFDoffset + 2 + i * IFDsize + 4, littleEndian) - const offset = dataview.getUint32(IFDoffset + 2 + i * IFDsize + 8, littleEndian) - return new TextDecoder().decode(dataview.buffer.slice(offset, offset + nbytes)) - } - } -} - -/** - * Parse TIFF metadata - * - * @param file - BIDSFile object - * @param OME - boolean to determine if OME metadata should be parsed - * - * @returns Object containing TIFF and OME metadata - */ -export async function parseTIFF( - file: BIDSFile, - OME: boolean, -): Promise<{ tiff?: Tiff; ome?: Ome }> { - const buf = await file.readBytes(4096) - const dataview = new DataView(buf.buffer, buf.byteOffset, buf.byteLength) - const magic = dataview.getUint16(0, true) - const littleEndian = magic === 0x4949 - const isTiff = littleEndian || magic === 0x4d4d - if (!isTiff) return {} - - const version = dataview.getUint16(2, littleEndian) - if (!OME) { - return { tiff: { version } } - } - - const imageDescription = getImageDescription(dataview, littleEndian, version === 42 ? 12 : 20) - const omexml = await XML.parse(imageDescription || '') as { [key: string]: any } - const Pixels = omexml?.OME?.Image?.Pixels - if (!Pixels) return { tiff: { version } } - - return { - tiff: { version }, - ome: { - PhysicalSizeX: parseFloat(Pixels['@PhysicalSizeX']), - PhysicalSizeY: parseFloat(Pixels['@PhysicalSizeY']), - PhysicalSizeZ: parseFloat(Pixels['@PhysicalSizeZ']), - PhysicalSizeXUnit: Pixels['@PhysicalSizeXUnit'], - PhysicalSizeYUnit: Pixels['@PhysicalSizeYUnit'], - PhysicalSizeZUnit: Pixels['@PhysicalSizeZUnit'], - }, - } -} diff --git a/bids-validator/src/files/tsv.ts b/bids-validator/src/files/tsv.ts deleted file mode 100644 index ea818e040..000000000 --- a/bids-validator/src/files/tsv.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * TSV - * Module for parsing TSV - */ -import { ColumnsMap } from '../types/columns.ts' -import type { BIDSFile } from '../types/filetree.ts' -import { filememoizeAsync } from '../utils/memoize.ts' -import type { WithCache } from '../utils/memoize.ts' - -const normalizeEOL = (str: string): string => str.replace(/\r\n/g, '\n').replace(/\r/g, '\n') -// Typescript resolved `row && !/^\s*$/.test(row)` as `string | boolean` -const isContentfulRow = (row: string): boolean => !!(row && !/^\s*$/.test(row)) - -async function _loadTSV(file: BIDSFile): Promise { - return await file.text().then(parseTSV) -} - -export const loadTSV = filememoizeAsync(_loadTSV) - -function parseTSV(contents: string) { - const columns = new ColumnsMap() - const rows: string[][] = normalizeEOL(contents) - .split('\n') - .filter(isContentfulRow) - .map((str) => str.split('\t')) - const headers = rows.length ? rows[0] : [] - - if (rows.some((row) => row.length !== headers.length)) { - throw { key: 'TSV_EQUAL_ROWS' } - } - - headers.map((x) => { - columns[x] = [] - }) - if (headers.length !== Object.keys(columns).length) { - throw { key: 'TSV_COLUMN_HEADER_DUPLICATE', evidence: headers.join(', ') } - } - for (let i = 1; i < rows.length; i++) { - for (let j = 0; j < headers.length; j++) { - const col = columns[headers[j]] as string[] - col.push(rows[i][j]) - } - } - return columns -} diff --git a/bids-validator/src/issues/datasetIssues.test.ts b/bids-validator/src/issues/datasetIssues.test.ts deleted file mode 100644 index 651cdbda4..000000000 --- a/bids-validator/src/issues/datasetIssues.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { assert, assertEquals, assertThrows } from '@std/assert' -import { pathsToTree } from '../files/filetree.ts' -import { DatasetIssues } from './datasetIssues.ts' - -Deno.test('DatasetIssues management class', async (t) => { - await t.step('Constructor succeeds', () => { - new DatasetIssues() - }) - await t.step('add Issue throws an error with bad error code', () => { - const issues = new DatasetIssues() - assertThrows(() => { - issues.add({ code: '__NOT_A_REAL_CODE__' }) - }) - }) - - await t.step('add Issue with several kinds of files', () => { - const root = pathsToTree([ - '/dataset_description.json', - '/README', - ]) - const issues = new DatasetIssues() - issues.add({ code: 'TEST_FILES_ERROR', location: root.files[1].path }, 'Test issue') - const foundIssue = issues.get({ location: '/README' }) - assertEquals(foundIssue.length, 1) - assertEquals(foundIssue[0].code, 'TEST_FILES_ERROR') - }) - - await t.step('test groupBy', () => { - const issues = new DatasetIssues() - issues.add({ code: 'NOT_INCLUDED', location: '/file_1' }) - issues.add({ code: 'NOT_INCLUDED', location: '/file_2' }) - issues.add({ code: 'EMPTY_FILE', location: '/file_1' }) - const byLoc = issues.groupBy('location') - assert(byLoc !== undefined) - const f1 = byLoc.get('/file_1') - const f2 = byLoc.get('/file_2') - assert(f1 !== undefined) - assert(f2 !== undefined) - assertEquals(f1.size, 2) - assertEquals(f2.size, 1) - - const byCode = issues.groupBy('code') - assert(byCode !== undefined) - const code1 = byCode.get('NOT_INCLUDED') - assert(code1 !== undefined) - assertEquals(code1.size, 2) - }) -}) diff --git a/bids-validator/src/issues/datasetIssues.ts b/bids-validator/src/issues/datasetIssues.ts deleted file mode 100644 index 18ecbbc23..000000000 --- a/bids-validator/src/issues/datasetIssues.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { nonSchemaIssues } from './list.ts' -import type { Issue, Severity, IssueDefinition, IssueFile } from '../types/issues.ts' -export type { Issue, Severity, IssueDefinition, IssueFile } - -// Code is deprecated, return something unusual but JSON serializable -const CODE_DEPRECATED = Number.MIN_SAFE_INTEGER - -type Group = Map - -export class DatasetIssues { - issues: Issue[] - codeMessages: Map - - constructor() { - this.issues = [] - this.codeMessages = new Map() - } - - add(issue: Issue, codeMessage?: string) { - if (!codeMessage) { - if (issue.code in nonSchemaIssues) { - codeMessage = nonSchemaIssues[issue.code].reason - issue.severity ??= nonSchemaIssues[issue.code].severity - } else { - throw new Error( - `key: ${issue.code} does not exist in non-schema issues definitions`, - ) - } - } - issue.severity ??= 'error' - if (!this.codeMessages.has(issue.code)) { - this.codeMessages.set(issue.code, codeMessage) - } - this.issues.push(issue) - } - - get(issue: Partial): Issue[] { - let found: Issue[] = this.issues - for (const key in issue) { - const value = issue[key as keyof Issue] - if (!value) { - continue - } - found = found.filter((x) => x[key as keyof Issue] === value) - } - return found - } - - filter(query: Partial): DatasetIssues { - const results = new DatasetIssues() - const found = this.get(query) - for (const issue of found) { - results.add(issue, this.codeMessages.get(issue.code)) - } - return results - } - - get size(): number { - return this.issues.length - } - - groupBy(key: keyof Issue): Map { - const groups: Map = new Map() - groups.set('None', new DatasetIssues()) - this.issues.map((issue) => { - let value: Issue[keyof Issue] = 'None' - if (key in issue && issue[key]) { - value = issue[key] - } - if (!groups.has(value)) { - groups.set(value, new DatasetIssues()) - } - // @ts-expect-error TS2532 return of get possible undefined. Does the above 'has' catch this case? - groups.get(value).add(issue, this.codeMessages.get(issue.code)) - }) - if (groups.has('None') && groups.get('None')?.size === 0) { - groups.delete('None') - } - return groups - } -} diff --git a/bids-validator/src/issues/list.test.ts b/bids-validator/src/issues/list.test.ts deleted file mode 100644 index 953b99d98..000000000 --- a/bids-validator/src/issues/list.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { assert, assertEquals } from '@std/assert' -import { loadSchema } from '../setup/loadSchema.ts' -import { bidsIssues } from './list.ts' -import type { GenericSchema } from '../types/schema.ts' - -interface schemaError { - code: string - level?: string -} - -function getSchemaErrors( - schema: GenericSchema, - rootSchema: GenericSchema, - schemaPath: string, -): schemaError[] { - const errors: schemaError[] = [] - for (const [key, value] of Object.entries(schema)) { - if (value.constructor !== Object) { - continue - } - errors.push(...getSchemaErrors( - value as GenericSchema, - rootSchema, - `${schemaPath}.${key}`, - )) - if ('code' in value && typeof value.code === 'string') { - errors.push(value as unknown as schemaError) - } - } - return errors -} - -Deno.test('Cross reference error codes in schema and in list.ts', async (t) => { - let errors: schemaError[] = [] - await t.step('load schema, get errors', async () => { - const rules = await loadSchema().then((schema) => schema['rules']) as GenericSchema - errors = getSchemaErrors(rules, rules, 'rules') - assert(errors.length > 1) - }) - - for (const error of errors) { - const code = error.code - await t.step(`check ${code}`, () => { - if (Object.hasOwn(bidsIssues, code) && Object.hasOwn(error, 'level')) { - assertEquals( - bidsIssues[code]['severity'], - error['level'], - `Severity mismatch on code ${code}`, - ) - } - }) - } -}) diff --git a/bids-validator/src/issues/list.ts b/bids-validator/src/issues/list.ts deleted file mode 100644 index 779a7cdef..000000000 --- a/bids-validator/src/issues/list.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { IssueDefinitionRecord } from '../types/issues.ts' - -export const bidsIssues: IssueDefinitionRecord = { - INVALID_JSON_ENCODING: { - severity: 'error', - reason: 'JSON files must be valid UTF-8 encoded text.', - }, - JSON_INVALID: { - severity: 'error', - reason: 'Not a valid JSON file.', - }, - MISSING_DATASET_DESCRIPTION: { - severity: 'error', - reason: 'A dataset_description.json file is required in the root of the dataset', - }, - INVALID_ENTITY_LABEL: { - severity: 'error', - reason: "entity label doesn't match format found for files with this suffix", - }, - ENTITY_WITH_NO_LABEL: { - severity: 'error', - reason: 'Found an entity with no label.', - }, - MISSING_REQUIRED_ENTITY: { - severity: 'error', - reason: 'Missing required entity for files with this suffix.', - }, - ENTITY_NOT_IN_RULE: { - severity: 'error', - reason: 'Entity not listed as required or optional for files with this suffix', - }, - DATATYPE_MISMATCH: { - severity: 'error', - reason: 'The datatype directory does not match datatype of found suffix and extension', - }, - ALL_FILENAME_RULES_HAVE_ISSUES: { - severity: 'error', - reason: - 'Multiple filename rules were found as potential matches. All of them had at least one issue during filename validation.', - }, - EXTENSION_MISMATCH: { - severity: 'error', - reason: 'Extension used by file does not match allowed extensions for its suffix', - }, - INVALID_LOCATION: { - severity: 'error', - reason: 'The file has a valid name, but is located in an invalid directory.', - }, - FILENAME_MISMATCH: { - severity: 'error', - reason: - 'The filename is not formatted correctly. This could result from entity duplication or reordering.', - }, - JSON_KEY_REQUIRED: { - severity: 'error', - reason: 'A JSON flle is missing a key listed as required.', - }, - JSON_KEY_RECOMMENDED: { - severity: 'warning', - reason: 'A JSON file is missing a key listed as recommended.', - }, - SIDECAR_KEY_REQUIRED: { - severity: 'error', - reason: "A data file's JSON sidecar is missing a key listed as required.", - }, - SIDECAR_KEY_RECOMMENDED: { - severity: 'warning', - reason: "A data file's JSON sidecar is missing a key listed as recommended.", - }, - JSON_SCHEMA_VALIDATION_ERROR: { - severity: 'error', - reason: 'Invalid JSON sidecar file. The sidecar is not formatted according the schema.', - }, - TSV_ERROR: { - severity: 'error', - reason: 'generic place holder for errors from tsv files', - }, - TSV_COLUMN_HEADER_DUPLICATE: { - severity: 'error', - reason: - 'Two elements in the first row of a TSV are the same. Each column header must be unique.', - }, - TSV_EQUAL_ROWS: { - severity: 'error', - reason: 'All rows must have the same number of columns as there are headers.', - }, - TSV_COLUMN_MISSING: { - severity: 'error', - reason: 'A required column is missing', - }, - TSV_COLUMN_ORDER_INCORRECT: { - severity: 'error', - reason: 'Some TSV columns are in the incorrect order', - }, - TSV_ADDITIONAL_COLUMNS_NOT_ALLOWED: { - severity: 'error', - reason: 'A TSV file has extra columns which are not allowed for its file type', - }, - TSV_ADDITIONAL_COLUMNS_MUST_DEFINE: { - severity: 'error', - reason: - 'Additional TSV columns must be defined in the associated JSON sidecar for this file type', - }, - TSV_ADDITIONAL_COLUMNS_UNDEFINED: { - severity: 'warning', - reason: 'A TSV file has extra columns which are not defined in its associated JSON sidecar', - }, - TSV_INDEX_VALUE_NOT_UNIQUE: { - severity: 'error', - reason: - 'An index column(s) was specified for the tsv file and not all of the values for it are unique.', - }, - TSV_VALUE_INCORRECT_TYPE: { - severity: 'error', - reason: - 'A value in a column did match the acceptable type for that column headers specified format.', - }, - TSV_VALUE_INCORRECT_TYPE_NONREQUIRED: { - severity: 'warning', - reason: - 'A value in a column did match the acceptable type for that column headers specified format.', - }, - TSV_COLUMN_TYPE_REDEFINED: { - severity: 'warning', - reason: - 'A column required in a TSV file has been redefined in a sidecar file. This redefinition is being ignored.', - }, - MULTIPLE_INHERITABLE_FILES: { - severity: 'error', - reason: 'Multiple files in a directory were found to be valid candidates for inheritance.', - }, - NIFTI_HEADER_UNREADABLE: { - severity: 'error', - reason: - 'We were unable to parse header data from this NIfTI file. Please ensure it is not corrupted or mislabeled.', - }, - CHECK_ERROR: { - severity: 'error', - reason: 'generic place holder for errors from failed `checks` evaluated from schema.', - }, - NOT_INCLUDED: { - severity: 'error', - reason: - 'Files with such naming scheme are not part of BIDS specification. This error is most commonly ' + - 'caused by typos in file names that make them not BIDS compatible. Please consult the specification and ' + - 'make sure your files are named correctly. If this is not a file naming issue (for example when including ' + - 'files not yet covered by the BIDS specification) you should include a ".bidsignore" file in your dataset (see' + - ' https://github.com/bids-standard/bids-validator#bidsignore for details). Please ' + - 'note that derived (processed) data should be placed in /derivatives folder and source data (such as DICOMS ' + - 'or behavioural logs in proprietary formats) should be placed in the /sourcedata folder.', - }, - EMPTY_FILE: { - severity: 'error', - reason: 'Empty files not allowed.', - }, - UNUSED_STIMULUS: { - severity: 'warning', - reason: - 'There are files in the /stimuli directory that are not utilized in any _events.tsv file.', - }, - SIDECAR_WITHOUT_DATAFILE: { - severity: 'error', - reason: 'A json sidecar file was found without a corresponding data file', - }, - BLACKLISTED_MODALITY: { - severity: 'error', - reason: 'The modality in this file is blacklisted through validator configuration.', - }, - CITATION_CFF_VALIDATION_ERROR: { - severity: 'error', - reason: - "The file does not pass validation using the citation.cff standard's schema." + - 'https://github.com/citation-file-format/citation-file-format/blob/main/schema-guide.md' - }, - FILE_READ: { - severity: 'error', - reason: 'We were unable to read this file.' - } -} - -const hedIssues: IssueDefinitionRecord = { - HED_ERROR: { - severity: 'error', - reason: 'The validation on this HED string returned an error.', - }, - HED_WARNING: { - severity: 'warning', - reason: 'The validation on this HED string returned a warning.', - }, - HED_INTERNAL_ERROR: { - severity: 'error', - reason: 'An internal error occurred during HED validation.', - }, - HED_INTERNAL_WARNING: { - severity: 'warning', - reason: 'An internal warning occurred during HED validation.', - }, - HED_MISSING_VALUE_IN_SIDECAR: { - severity: 'warning', - reason: - 'The json sidecar does not contain this column value as a possible key to a HED string.', - }, - HED_VERSION_NOT_DEFINED: { - severity: 'warning', - reason: - "You should define 'HEDVersion' for this file. If you don't provide this information, the HED validation will use the latest version available.", - }, -} - -export const hedOldToNewLookup: Record> = { - 104: 'HED_ERROR', - 105: 'HED_WARNING', - 106: 'HED_INTERNAL_ERROR', - 107: 'HED_INTERNAL_WARNING', - 108: 'HED_MISSING_VALUE_IN_SIDECAR', - 109: 'HED_VERSION_NOT_DEFINED', -} - -export const nonSchemaIssues = { ...bidsIssues, ...hedIssues } diff --git a/bids-validator/src/main.ts b/bids-validator/src/main.ts deleted file mode 100644 index eb9c835fb..000000000 --- a/bids-validator/src/main.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { parseOptions } from './setup/options.ts' -import type { Config } from './setup/options.ts' -import * as colors from '@std/fmt/colors' -import { readFileTree } from './files/deno.ts' -import { fileListToTree } from './files/browser.ts' -import { resolve } from '@std/path' -import { validate } from './validators/bids.ts' -import { consoleFormat, resultToJSONStr } from './utils/output.ts' -import { setupLogging } from './utils/logger.ts' -import type { ValidationResult } from './types/validation-result.ts' -export type { ValidationResult } from './types/validation-result.ts' - -/** - * Validation entrypoint intended for command line usage with Deno - * - * Parses command line options, runs validation, and formats the result. Call `validate` directly for other environments - */ -export async function main(): Promise { - const options = await parseOptions(Deno.args) - colors.setColorEnabled(options.color ?? false) - setupLogging(options.debug) - - const absolutePath = resolve(options.datasetPath) - const tree = await readFileTree(absolutePath) - - const config = options.config ? JSON.parse(Deno.readTextFileSync(options.config)) as Config : {} - - // Run the schema based validator - const schemaResult = await validate(tree, options, config) - - let output_string = "" - if (options.json) { - output_string = resultToJSONStr(schemaResult) - } else { - output_string = consoleFormat(schemaResult, { - verbose: options.verbose ? options.verbose : false, - }) - } - - if (options.outfile) { - if (globalThis.Deno) { - Deno.writeTextFileSync(options.outfile, output_string) - } else { - console.error("Output to file only supported in Deno runtime") - console.log(output_string) - } - } else { - console.log(output_string) - } - - return schemaResult -} - -export { fileListToTree, validate } diff --git a/bids-validator/src/schema/applyRules.test.ts b/bids-validator/src/schema/applyRules.test.ts deleted file mode 100644 index 0d7af05a9..000000000 --- a/bids-validator/src/schema/applyRules.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -// @ts-nocheck -import { assert, assertEquals } from '@std/assert' -import { applyRules, evalCheck, evalConstructor } from './applyRules.ts' -import { DatasetIssues } from '../issues/datasetIssues.ts' -import { expressionFunctions } from './expressionLanguage.ts' - -const ruleContextData = [ - { - path: ['rules', 'checks', 'dwi', 'DWIVolumeCount'], - context: { - suffix: 'dwi', - associations: { - bvec: { - n_cols: 4, - }, - bval: { - n_cols: 4, - }, - }, - nifti_header: { - dim: [0, 0, 0, 0, 4], - }, - }, - }, -] - -const schemaDefs = { - rules: { - checks: { - dwi: { - DWIVolumeCount: { - code: 'VOLUME_COUNT_MISMATCH', - description: - 'The number of volumes in this scan does not match the number of volumes in the\ncorresponding .bvec a...', - level: 'error', - selectors: [ - 'suffix == "dwi"', - '"bval" in associations', - '"bvec" in associations', - ], - checks: [ - 'associations.bval.n_cols == nifti_header.dim[4]', - 'associations.bvec.n_cols == nifti_header.dim[4]', - ], - }, - }, - }, - }, -} - -Deno.test('evalCheck test', () => { - ruleContextData.map((rcd) => { - const rule = rcd.path.reduce((obj, key) => obj[key], schemaDefs) - rule.selectors.map((selector: string) => { - assert(evalCheck(selector, rcd.context), `${selector}, ${rcd.context}`) - }) - rule.checks.map((check: string) => { - assert(evalCheck(check, rcd.context), `${check}, ${rcd.context}`) - }) - }) -}) - -Deno.test('evalCheck ensure constructor access', () => { - assert( - evalCheck('foo.constructor.isArray(foo)', { foo: [1] }), - 'can not access Array prototype via constructor', - ) -}) - -Deno.test('evalCheck built in apis fail', () => { - assert(evalCheck('fetch', {}) === undefined, 'fetch in evalCheck namespace') -}) - -Deno.test('evalCheck ensure expression language functions work', () => { - const context = { - x: [1, 2, 3, 4], - y: [1, 1, 1, 1], - dataset: { issues: new DatasetIssues() }, - } - const rule = [ - { - selectors: ['true'], - checks: [ - 'intersects(x, y)', - 'match("teststr", "est")', - 'type(x) == "array" && type(5) == "number"', - 'max(x) == 4', - 'min(x) == min(y)', - 'length(y) == count(y, 1)', - ], - }, - ] - applyRules(rule, context) - assertEquals(context.dataset.issues.get({ code: 'CHECK_ERROR' }).length, 0) -}) -Deno.test( - 'evalCheck ensure expression language will fail appropriately', - () => { - const context = { dataset: { issues: new DatasetIssues() } } - const rule = [ - { - selectors: ['true'], - checks: ['length(1)'], - }, - ] - applyRules(rule, context) - assertEquals(context.dataset.issues.get({ code: 'CHECK_ERROR' }).length, 1) - }, -) - -Deno.test('evalConstructor test', async (t) => { - const match = expressionFunctions.match - await t.step('check veridical reconstruction of match expressions', () => { - // match() functions frequently contain escapes in regex patterns - // We receive these from the schema as written in YAML, so we need to ensure - // that they are correctly reconstructed in the evalConstructor() function - // - // The goal is to avoid schema authors needing to double-escape their regex patterns - // and other implementations to account for the double-escaping - let pattern = String.raw`^\.nii(\.gz)?$` - - // Check both a literal and a variable pattern produce the same result - for (const check of ['match(extension, pattern)', `match(extension, '${pattern}')`]) { - const niftiCheck = evalConstructor(check) - for ( - const [extension, expected] of [ - ['.nii', true], - ['.nii.gz', true], - ['.tsv', false], - [',nii,gz', false], // Check that . is not treated as a wildcard - ] - ) { - assert(match(extension, pattern) === expected) - // Pass in a context object to provide any needed variables - assert(niftiCheck({ match, extension, pattern }) === expected) - } - } - - pattern = String.raw`\S` - for (const check of ['match(json.Name, pattern)', `match(json.Name, '${pattern}')`]) { - const nonEmptyCheck = evalConstructor(check) - for ( - const [Name, expected] of [ - ['test', true], - ['', false], - [' ', false], - ] - ) { - assert(match(Name, pattern) === expected) - assert(nonEmptyCheck({ match, json: { Name }, pattern }) === expected) - } - } - }) -}) diff --git a/bids-validator/src/schema/applyRules.ts b/bids-validator/src/schema/applyRules.ts deleted file mode 100644 index 7d9ed80a4..000000000 --- a/bids-validator/src/schema/applyRules.ts +++ /dev/null @@ -1,291 +0,0 @@ -import type { GenericRule, GenericSchema, SchemaFields, SchemaTypeLike } from '../types/schema.ts' -import type { Severity } from '../types/issues.ts' -import type { BIDSContext } from './context.ts' -import { expressionFunctions } from './expressionLanguage.ts' -import { logger } from '../utils/logger.ts' -import { memoize } from '../utils/memoize.ts' -import { compile } from '../validators/json.ts' -import type { DefinedError } from '@ajv' -import { - evalAdditionalColumns, - evalColumns, - evalIndexColumns, - evalInitialColumns, -} from './tables.ts' - -/** - * Given a schema and context, evaluate which rules match and test them. - * Recursively descend into schema object and iterate over each levels keys. - * If we find a child of the object that isn't an Object ignore it, this will - * be things that show up in meta and objects directories. If an an object - * has a selectors key we know that this is an actual rule that we know how - * to evaluate. Finally if what we have is an Object recurse on it to see if - * its children have any rules. - * @param schema - * @param context - */ -export function applyRules( - schema: GenericSchema, - context: BIDSContext, - rootSchema?: GenericSchema, - schemaPath?: string, -) { - if (!rootSchema) { - rootSchema = schema - } - /* Normal run of validation starts at schema.json root, but some tests will pass - in truncated schemas. Only set origin of rules to rules object if we see it. - */ - if (schemaPath === undefined) { - if (Object.hasOwn(schema, 'rules')) { - schemaPath = 'rules' - // @ts-expect-error - schema = schema.rules - } else { - schemaPath = '' - } - } - Object.assign(context, expressionFunctions) - // @ts-expect-error - context.exists.bind(context) - for (const key in schema) { - if (!(schema[key].constructor === Object)) { - continue - } - if ('selectors' in schema[key]) { - evalRule( - schema[key] as GenericRule, - context, - rootSchema, - `${schemaPath}.${key}`, - ) - } else if (schema[key].constructor === Object) { - applyRules( - schema[key] as GenericSchema, - context, - rootSchema, - `${schemaPath}.${key}`, - ) - } - } - return Promise.resolve() -} - -export const evalConstructor = (src: string): Function => - new Function('context', `with (context) { return ${src.replace(/\\/g, '\\\\')} }`) -const safeHas = () => true -const safeGet = (target: any, prop: any) => prop === Symbol.unscopables ? undefined : target[prop] - -const memoizedEvalConstructor = memoize(evalConstructor) - -export function evalCheck(src: string, context: BIDSContext) { - const test = memoizedEvalConstructor(src) - const safeContext = new Proxy(context, { has: safeHas, get: safeGet }) - try { - return test(safeContext) - } catch (error) { - logger.debug(error) - return null - } -} - -/** - * Different keys in a rule have different interpretations. - * We associate theys keys from a rule object to a function adds an - * issue to the context if the rule evaluation fails. - */ -// @ts-expect-error -const evalMap: Record< - keyof GenericRule, - ( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, - ) => boolean | void -> = { - checks: evalRuleChecks, - columns: evalColumns, - additional_columns: evalAdditionalColumns, - initial_columns: evalInitialColumns, - index_columns: evalIndexColumns, - fields: evalJsonCheck, -} - -/** - * Entrypoint for evaluating a individual rule. - * We see if every selector applies to this context, - * Then we attempt to interpret every other key in the rule - * object. - */ -function evalRule( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -) { - if (rule.selectors && !mapEvalCheck(rule.selectors, context)) { - return - } - Object.keys(rule) - .filter((key) => key in evalMap) - .map((key) => { - // @ts-expect-error - evalMap[key](rule, context, schema, schemaPath) - }) -} - -function mapEvalCheck(statements: string[], context: BIDSContext): boolean { - return statements.every((x) => evalCheck(x, context)) -} - -/** - * Classic rules interpreted like selectors. Examples in specification: - * schema/rules/checks/* - */ -function evalRuleChecks( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -): boolean { - if (rule.checks && !mapEvalCheck(rule.checks, context)) { - if (rule.issue?.code && rule.issue?.message) { - context.dataset.issues.add({ - code: rule.issue.code, - location: context.path, - rule: schemaPath, - severity: rule.issue.level as Severity, - }, rule.issue.message) - } else { - context.dataset.issues.add( - { code: 'CHECK_ERROR', location: context.path, rule: schemaPath }, - ) - } - } - return true -} - -/** - * For evaluating field requirements and values that should exist in a json - * sidecar for a file. Will need to implement an additional check/error for - * `prohibitied` fields. Examples in specification: - * schema/rules/sidecars/* - */ -function evalJsonCheck( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -): void { - const sidecarRule = schemaPath.match(/rules\.sidecar/) - // Sidecar rules apply specifically to data files, as JSON files cannot have sidecars - // Count on other JSON rules to use selectors to match the correct files - if (context.extension === '.json' && sidecarRule) return - - const json: Record = sidecarRule ? context.sidecar : context.json - for (const [key, requirement] of Object.entries(rule.fields)) { - // @ts-expect-error - const metadataDef = schema.objects.metadata[key] - const keyName: string = metadataDef.name - const value = json[keyName] - if (value === undefined) { - const severity = getFieldSeverity(requirement, context) - if (severity && severity !== 'ignore') { - if (requirement.issue?.code && requirement.issue?.message) { - context.dataset.issues.add({ - code: requirement.issue.code, - subCode: keyName, - location: context.path, - severity, - rule: schemaPath, - }, requirement.issue.message) - } else { - let code - const keyType = sidecarRule ? 'SIDECAR_KEY' : 'JSON_KEY' - const level = severity === 'error' ? 'REQUIRED' : 'RECOMMENDED' - context.dataset.issues.add({ - code: `${keyType}_${level}`, - subCode: keyName, - location: context.path, - severity, - rule: schemaPath, - }) - } - } - - /* Regardless of if key is required/recommended/optional, we do no - * further valdiation if it is not present in sidecar. - */ - continue - } - - if (sidecarRule && !(keyName in context.sidecarKeyOrigin)) { - logger.warn( - `sidecarKeyOrigin map failed to initialize for ${context.path} on key ${keyName}. Validation caching not active for this key.`, - ) - } - - const location = sidecarRule ? (context.sidecarKeyOrigin[keyName] ?? '') : context.path - const affects = sidecarRule ? [context.path] : undefined - - const keyAddress = `${location}:${keyName}` - - if (sidecarRule && context.dataset.sidecarKeyValidated.has(keyAddress)) { - continue - } - - const validate = compile(metadataDef) - if (!validate(value)) { - for (const err of validate.errors as DefinedError[]) { - context.dataset.issues.add({ - code: 'JSON_SCHEMA_VALIDATION_ERROR', - subCode: keyName, - issueMessage: err['message'], - rule: schemaPath, - location, - affects, - }) - } - } - if (sidecarRule && location) { - context.dataset.sidecarKeyValidated.add(keyAddress) - } - } -} - -/** - * JSON Field checks have conditions where their requirement levels can - * change based on some other field. This function resolves the severity - * of a JsonCheckFailure depending on how the checks level object is shaped. - */ -function getFieldSeverity( - requirement: string | SchemaFields, - context: BIDSContext, -): Severity { - // Does this conversion hold for other parts of the schema or just json checks? - const levelToSeverity: Record = { - recommended: 'warning', - required: 'error', - optional: 'ignore', - prohibited: 'ignore', - } - let severity: Severity = 'ignore' - - if (typeof requirement === 'string' && requirement in levelToSeverity) { - severity = levelToSeverity[requirement] - } else if (typeof requirement === 'object' && requirement.level) { - severity = levelToSeverity[requirement.level] - const addendumRegex = /(required|recommended) if \`(\w+)\` is \`(\w+)\`/ - if (requirement.level_addendum) { - const match = addendumRegex.exec(requirement.level_addendum) - if (match && match.length === 4) { - const [_, addendumLevel, key, value] = match - if (key in context.sidecar && context.sidecar[key] === value) { - severity = levelToSeverity[addendumLevel] - } - } - } - } - return severity -} diff --git a/bids-validator/src/schema/associations.ts b/bids-validator/src/schema/associations.ts deleted file mode 100644 index fea52a82e..000000000 --- a/bids-validator/src/schema/associations.ts +++ /dev/null @@ -1,184 +0,0 @@ -import type { - Aslcontext, - Associations, - Bval, - Bvec, - Channels, - Coordsystem, - Events, - M0Scan, - Magnitude, - Magnitude1, -} from '@bids/schema/context' -import type { BIDSFile, FileTree } from '../types/filetree.ts' -import type { BIDSContext } from './context.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import type { readEntities } from './entities.ts' -import { loadTSV } from '../files/tsv.ts' -import { parseBvalBvec } from '../files/dwi.ts' -import { walkBack } from '../files/inheritance.ts' - -// type AssociationsLookup = Record => { - const columns = await loadTSV(file) - .catch((e) => { - return new Map() - }) - return { - path: file.path, - onset: columns.get('onset') || [], - } - }, - }, - aslcontext: { - suffix: 'aslcontext', - extensions: ['.tsv'], - inherit: true, - load: async ( - file: BIDSFile, - ): Promise => { - const columns = await loadTSV(file) - .catch((e) => { - return new Map() - }) - return { - path: file.path, - n_rows: columns.get('volume_type')?.length || 0, - volume_type: columns.get('volume_type') || [], - } - }, - }, - m0scan: { - suffix: 'm0scan', - extensions: ['.nii', '.nii.gz'], - inherit: false, - load: (file: BIDSFile): Promise => { - return Promise.resolve({ path: file.path }) - }, - }, - magnitude: { - suffix: 'magnitude', - extensions: ['.nii', '.nii.gz'], - inherit: false, - load: (file: BIDSFile): Promise => { - return Promise.resolve({ path: file.path }) - }, - }, - magnitude1: { - suffix: 'magnitude1', - extensions: ['.nii', '.nii.gz'], - inherit: false, - load: (file: BIDSFile): Promise => { - return Promise.resolve({ path: file.path }) - }, - }, - bval: { - suffix: 'dwi', - extensions: ['.bval'], - inherit: true, - load: async (file: BIDSFile): Promise => { - const contents = await file.text() - const rows = parseBvalBvec(contents) - return { - path: file.path, - n_cols: rows ? rows[0].length : 0, - n_rows: rows ? rows.length : 0, - // @ts-expect-error values is expected to be a number[], coerce lazily - values: rows[0], - } - }, - }, - bvec: { - suffix: 'dwi', - extensions: ['.bvec'], - inherit: true, - load: async (file: BIDSFile): Promise => { - const contents = await file.text() - const rows = parseBvalBvec(contents) - - if (rows.some((row) => row.length !== rows[0].length)) { - throw { key: 'BVEC_ROW_LENGTH' } - } - - return { - path: file.path, - n_cols: rows ? rows[0].length : 0, - n_rows: rows ? rows.length : 0, - } - }, - }, - channels: { - suffix: 'channels', - extensions: ['.tsv'], - inherit: true, - load: async (file: BIDSFile): Promise => { - const columns = await loadTSV(file) - .catch((e) => { - return new Map() - }) - return { - path: file.path, - type: columns.get('type'), - short_channel: columns.get('short_channel'), - sampling_frequency: columns.get('sampling_frequency'), - } - }, - }, - coordsystem: { - suffix: 'coordsystem', - extensions: ['.json'], - inherit: true, - load: (file: BIDSFile): Promise => { - return Promise.resolve({ path: file.path }) - }, - }, -} - -export async function buildAssociations( - source: BIDSFile, - issues: DatasetIssues, -): Promise { - const associations: Associations = {} - - for (const [key, value] of Object.entries(associationLookup)) { - const { suffix, extensions, inherit, load } = value - let file - try { - file = walkBack(source, inherit, extensions, suffix).next().value - } catch (error) { - if (error.code === 'MULTIPLE_INHERITABLE_FILES') { - issues.add(error) - break - } else { - throw error - } - } - - if (file) { - // @ts-expect-error Matching load return value to key is hard - associations[key] = await load(file).catch((error) => { - if (error.key) { - issues.add({ code: error.key, location: file.path }) - } - }) - } - } - return Promise.resolve(associations) -} diff --git a/bids-validator/src/schema/context.test.ts b/bids-validator/src/schema/context.test.ts deleted file mode 100644 index cdeef6469..000000000 --- a/bids-validator/src/schema/context.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { assert, assertObjectMatch } from '@std/assert' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import { BIDSContext } from './context.ts' -import { dataFile, rootFileTree } from './fixtures.test.ts' - -Deno.test('test context LoadSidecar', async (t) => { - const context = new BIDSContext(dataFile) - await context.loadSidecar() - await t.step('sidecar overwrites correct fields', () => { - const { rootOverwrite, subOverwrite } = context.sidecar - assertObjectMatch(context.sidecar, { - rootOverwrite: 'anat', - subOverwrite: 'anat', - }) - }) - await t.step('sidecar adds new fields at each level', () => { - const { rootValue, subValue, anatValue } = context.sidecar - assertObjectMatch(context.sidecar, { - rootValue: 'root', - subValue: 'subject', - anatValue: 'anat', - }) - }) -}) - -Deno.test('test context loadSubjects', async (t) => { - const context = new BIDSContext(dataFile, undefined, rootFileTree) - await context.loadSubjects() - await t.step('context produces correct subjects object', () => { - assert(context.dataset.subjects, 'subjects object exists') - assert(context.dataset.subjects.sub_dirs.length == 1, 'there is only one sub dir found') - assert(context.dataset.subjects.sub_dirs[0] == 'sub-01', 'that sub dir is sub-01') - // no participants.tsv so this should be empty - assert(context.dataset.subjects.participant_id == undefined, 'no participant_id is populated') - }) -}) diff --git a/bids-validator/src/schema/context.ts b/bids-validator/src/schema/context.ts deleted file mode 100644 index 9adca5d41..000000000 --- a/bids-validator/src/schema/context.ts +++ /dev/null @@ -1,337 +0,0 @@ -import type { - Associations, - Context, - Dataset, - Gzip, - NiftiHeader, - Ome, - Subject, - Subjects, - Tiff, -} from '@bids/schema/context' -import type { Schema } from '../types/schema.ts' -import type { BIDSFile } from '../types/filetree.ts' -import { FileTree } from '../types/filetree.ts' -import { ColumnsMap } from '../types/columns.ts' -import { readEntities } from './entities.ts' -import { DatasetIssues } from '../issues/datasetIssues.ts' -import { walkBack } from '../files/inheritance.ts' -import { parseGzip } from '../files/gzip.ts' -import { loadTSV } from '../files/tsv.ts' -import { parseTIFF } from '../files/tiff.ts' -import { loadJSON } from '../files/json.ts' -import { loadHeader } from '../files/nifti.ts' -import { buildAssociations } from './associations.ts' -import type { ValidatorOptions } from '../setup/options.ts' -import { logger } from '../utils/logger.ts' - -export class BIDSContextDataset implements Dataset { - #dataset_description: Record = {} - tree: FileTree - ignored: string[] - datatypes: string[] - modalities: string[] - subjects: Subjects - - issues: DatasetIssues - sidecarKeyValidated: Set - options?: ValidatorOptions - schema: Schema - pseudofileExtensions: Set - - constructor( - args: Partial, - ) { - this.schema = args.schema || {} as unknown as Schema - this.dataset_description = args.dataset_description || {} - this.tree = args.tree || new FileTree('/unknown', 'unknown') - this.ignored = args.ignored || [] - this.datatypes = args.datatypes || [] - this.modalities = args.modalities || [] - this.sidecarKeyValidated = new Set() - if (args.options) { - this.options = args.options - } - this.issues = args.issues || new DatasetIssues() - this.pseudofileExtensions = new Set( - args.schema - ? Object.values(this.schema.objects.extensions) - ?.map((ext) => ext.value) - ?.filter((ext) => ext.endsWith('/')) - : [], - ) - this.subjects = args.subjects || { - sub_dirs: this.tree.directories.map((dir) => dir.name).filter((dir) => - dir.startsWith('sub-') - ), - } - } - - get dataset_description(): Record { - return this.#dataset_description - } - set dataset_description(value: Record) { - this.#dataset_description = value - if (!this.dataset_description.DatasetType) { - this.dataset_description.DatasetType = this.dataset_description.GeneratedBy - ? 'derivative' - : 'raw' - } - } - - isPseudoFile(file: FileTree): boolean { - const { suffix, extension, entities } = readEntities(file.name) - return ( - suffix !== '' && - Object.keys(entities).length > 0 && - this.pseudofileExtensions.has(`${extension}/`) - ) - } -} - -class BIDSContextDatasetSubjects implements Subjects { - sub_dirs: string[] - participant_id?: string[] - phenotype?: string[] - - constructor( - sub_dirs?: string[], - participant_id?: string[], - phenotype?: string[], - ) { - this.sub_dirs = sub_dirs ? sub_dirs : [] - this.participant_id = participant_id - this.phenotype = phenotype - } -} - -export class BIDSContext implements Context { - dataset: BIDSContextDataset - subject: Subject - // path: string <- getter - // size: number <- getter - entities: Record - datatype: string - suffix: string - extension: string - modality: string - sidecar: Record - associations: Associations - columns: Record - json: Record - gzip?: Gzip - nifti_header?: NiftiHeader - ome?: Ome - tiff?: Tiff - - file: BIDSFile - filenameRules: string[] - sidecarKeyOrigin: Record - - constructor( - file: BIDSFile, - dsContext?: BIDSContextDataset, - fileTree?: FileTree, - ) { - this.filenameRules = [] - this.file = file - const bidsEntities = readEntities(file.name) - this.suffix = bidsEntities.suffix - this.extension = bidsEntities.extension - this.entities = bidsEntities.entities - this.dataset = dsContext ? dsContext : new BIDSContextDataset({ tree: fileTree }) - this.subject = {} as Subject - this.datatype = '' - this.modality = '' - this.sidecar = {} - this.sidecarKeyOrigin = {} - this.columns = new ColumnsMap() as Record - this.json = {} - this.associations = {} as Associations - } - - get schema(): Schema { - return this.dataset.schema - } - - get size(): number { - return this.file.size - } - - get path(): string { - return this.file.path - } - - /** - * Implementation specific absolute path for the dataset root - * - * In the browser, this is always at the root - */ - get datasetPath(): string { - return this.dataset.tree.path - } - - /** - * Walks the fileTree backwards from the current file to the root, - * loading any valid json sidecars found. - * Earlier (deeper) sidecars take precedence over later ones. - */ - async loadSidecar() { - if (this.extension === '.json') { - return - } - let sidecars: BIDSFile[] = [] - try { - sidecars = [...walkBack(this.file)] - } catch (error) { - if (error.code === 'MULTIPLE_INHERITABLE_FILES') { - this.dataset.issues.add(error) - } else { - throw error - } - } - for (const file of sidecars) { - const json = await loadJSON(file).catch((error) => { - if (error.key) { - this.dataset.issues.add({ code: error.key, location: file.path }) - return {} - } else { - throw error - } - }) - this.sidecar = { ...json, ...this.sidecar } - Object.keys(json).map((x) => this.sidecarKeyOrigin[x] ??= file.path) - } - // Hack: round RepetitionTime to 3 decimal places; schema should add rounding function - if (typeof this.sidecar.RepetitionTime === 'number') { - this.sidecar.RepetitionTime = Math.round(this.sidecar.RepetitionTime * 1000) / 1000 - } - } - - async loadNiftiHeader(): Promise { - if ( - !this.extension.startsWith('.nii') || this.dataset?.options?.ignoreNiftiHeaders - ) return - - this.nifti_header = await loadHeader(this.file).catch((error) => { - if (error.key) { - this.dataset.issues.add({ code: error.key, location: this.file.path }) - return undefined - } else { - throw error - } - }) - } - - async loadColumns(): Promise { - if (this.extension !== '.tsv') { - return - } - - this.columns = await loadTSV(this.file) - .catch((error) => { - if (error.key) { - this.dataset.issues.add({ code: error.key, location: this.file.path }) - } - logger.warn( - `tsv file could not be opened by loadColumns '${this.file.path}'`, - ) - logger.debug(error) - return new Map() as ColumnsMap - }) as Record - return - } - - async loadAssociations(): Promise { - this.associations = await buildAssociations(this.file, this.dataset.issues) - return - } - - async loadJSON(): Promise { - if (this.extension !== '.json') { - return - } - this.json = await loadJSON(this.file).catch((error) => { - if (error.key) { - this.dataset.issues.add({ code: error.key, location: this.file.path }) - return {} - } else { - throw error - } - }) - } - - async loadGzip(): Promise { - if (!this.extension.endsWith('.gz')) { - return - } - this.gzip = await parseGzip(this.file, 512).catch((error) => { - logger.debug('Error parsing gzip header', error) - return undefined - }) - } - - async loadTIFF(): Promise { - if (!this.extension.endsWith('.tif') && !this.extension.endsWith('.btf')) { - return - } - const { tiff, ome } = await parseTIFF(this.file, this.extension.startsWith('.ome')).catch( - (error) => { - logger.debug('Error parsing tiff header', error) - return { tiff: undefined, ome: undefined } - }, - ) - this.tiff = tiff - this.ome = ome - } - - // This is currently done for every file. It should be done once for the dataset. - async loadSubjects(): Promise { - if (this.dataset.subjects != null) { - return - } - this.dataset.subjects = new BIDSContextDatasetSubjects() - // Load subject dirs from the file tree - this.dataset.subjects.sub_dirs = this.dataset.tree.directories - .filter((dir) => dir.name.startsWith('sub-')) - .map((dir) => dir.name) - - // Load participants from participants.tsv - const participants_tsv = this.dataset.tree.get('participants.tsv') as BIDSFile - if (participants_tsv) { - const participantsData = await loadTSV(participants_tsv) - this.dataset.subjects.participant_id = participantsData[ - 'participant_id' - ] as string[] - } - - // Load phenotype from phenotype/*.tsv - const phenotype_dir = this.dataset.tree.get('phenotype') as FileTree - if (phenotype_dir) { - const phenotypeFiles = phenotype_dir.files.filter((file) => file.name.endsWith('.tsv')) - // Collect observed participant_ids - const seen = new Set() as Set - for (const file of phenotypeFiles) { - const phenotypeData = await loadTSV(file) - const participant_id = phenotypeData['participant_id'] as string[] - if (participant_id) { - participant_id.forEach((id) => seen.add(id)) - } - } - this.dataset.subjects.phenotype = Array.from(seen) - } - } - - async asyncLoads() { - await Promise.allSettled([ - this.loadSubjects(), - this.loadSidecar(), - this.loadColumns(), - this.loadAssociations(), - this.loadNiftiHeader(), - this.loadJSON(), - this.loadGzip(), - this.loadTIFF(), - ]) - } -} diff --git a/bids-validator/src/schema/entities.test.ts b/bids-validator/src/schema/entities.test.ts deleted file mode 100644 index 2c2886ec1..000000000 --- a/bids-validator/src/schema/entities.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { assert, assertObjectMatch } from '@std/assert' -import { readEntities } from './entities.ts' -import { nullReadBytes } from '../tests/nullReadBytes.ts' -import { generateBIDSFilename } from '../tests/generate-filenames.ts' - -Deno.test('test readEntities', async (t) => { - await t.step('test readEntities with a BIDSFile-like object', async () => { - const testFile = { - name: 'task-rhymejudgment_bold.json', - path: '/task-rhymejudgment_bold.json', - size: null as unknown as number, - ignored: false, - stream: null as unknown as ReadableStream, - text: () => Promise.resolve(''), - readBytes: nullReadBytes, - } - const context = readEntities(testFile.name) - assert(context.stem === 'task-rhymejudgment_bold', 'failed to match stem') - assert(context.extension === '.json', 'failed to match extension') - assert(context.entities.task === 'rhymejudgment', 'failed to match entity') - assert(context.suffix === 'bold', 'failed to match suffix') - }) - - await t.step('test readEntities("sub-01")', async () => { - assertObjectMatch(readEntities('sub-01'), { - stem: 'sub-01', - entities: { sub: '01' }, - suffix: '', - extension: '', - }) - }) - await t.step('test readEntities("dataset_description.json")', async () => { - assertObjectMatch(readEntities('dataset_description.json'), { - stem: 'dataset_description', - entities: { 'dataset': 'NOENTITY' }, - suffix: 'description', - extension: '.json', - }) - }) - await t.step('test readEntities("participants.tsv")', async () => { - assertObjectMatch(readEntities('participants.tsv'), { - stem: 'participants', - entities: {}, - suffix: 'participants', - extension: '.tsv', - }) - }) - await t.step('test readEntities("sub-01_ses-01_T1w.nii.gz")', async () => { - assertObjectMatch(readEntities('sub-01_ses-01_T1w.nii.gz'), { - stem: 'sub-01_ses-01_T1w', - entities: { sub: '01', ses: '01' }, - suffix: 'T1w', - extension: '.nii.gz', - }) - }) - await t.step('test readEntities("sub-01_SEM.ome.zarr")', async () => { - assertObjectMatch(readEntities('sub-01_SEM.ome.zarr'), { - stem: 'sub-01_SEM', - entities: { sub: '01' }, - suffix: 'SEM', - extension: '.ome.zarr', - }) - }) - await t.step('test readEntities("sub-01_task-nback_meg")', async () => { - assertObjectMatch(readEntities('sub-01_task-nback_meg'), { - stem: 'sub-01_task-nback_meg', - entities: { sub: '01', task: 'nback' }, - suffix: 'meg', - extension: '', - }) - }) -}) diff --git a/bids-validator/src/schema/entities.ts b/bids-validator/src/schema/entities.ts deleted file mode 100644 index 5c31c7308..000000000 --- a/bids-validator/src/schema/entities.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { memoize } from '../utils/memoize.ts' - -interface BIDSFileParts { - stem: string - suffix: string - extension: string - entities: Record -} - -function _readEntities(filename: string): BIDSFileParts { - let suffix = '' - const entities: Record = {} - - const dot = filename.indexOf('.') - const [stem, extension] = dot === -1 - ? [filename, ''] - : [filename.slice(0, dot), filename.slice(dot)] - - const parts = stem.split('_') - if (parts.length > 1 || parts[parts.length - 1].match(/^[a-zA-Z0-9]+$/)) { - suffix = parts.pop() as string - } - for (const part of parts) { - // Use capturing regex to split on the first hyphen only - const [entity, label] = part.split(/-(.+)/) - entities[entity] = label || 'NOENTITY' - } - - return { stem, entities, suffix, extension } -} - -export const readEntities = memoize(_readEntities) diff --git a/bids-validator/src/schema/expressionLanguage.test.ts b/bids-validator/src/schema/expressionLanguage.test.ts deleted file mode 100644 index 742300bd1..000000000 --- a/bids-validator/src/schema/expressionLanguage.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { assert } from '@std/assert' -import { expressionFunctions } from './expressionLanguage.ts' -import { dataFile, rootFileTree } from './fixtures.test.ts' -import { BIDSContext } from './context.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' - -Deno.test('test expression functions', async (t) => { - const context = new BIDSContext(dataFile, undefined, rootFileTree) - - await t.step('index function', () => { - const index = expressionFunctions.index - assert(index([1, 2, 3], 2) === 1) - assert(index([1, 2, 3], 4) === null) - assert(index(['a', 'b', 'c'], 'b') === 1) - assert(index(['a', 'b', 'c'], 'd') === null) - }) - await t.step('intersects function', () => { - const intersects = expressionFunctions.intersects - assert(intersects([1, 2, 3], [2, 3, 4]) === true) - assert(intersects([1, 2, 3], [4, 5, 6]) === false) - assert(intersects(['abc', 'def'], ['def']) === true) - assert(intersects(['abc', 'def'], ['ghi']) === false) - - // Promote scalars to arrays - // @ts-ignore - assert(intersects('abc', ['abc', 'def']) === true) - // @ts-ignore - assert(intersects('abc', ['a', 'b', 'c']) === false) - }) - await t.step('match function', () => { - const match = expressionFunctions.match - assert(match('abc', 'abc') === true) - assert(match('abc', 'def') === false) - assert(match('abc', 'a.*') === true) - assert(match('abc', 'd.*') === false) - }) - await t.step('type function', () => { - const type = expressionFunctions.type - assert(type(1) === 'number') - assert(type('abc') === 'string') - assert(type([1, 2, 3]) === 'array') - assert(type({ a: 1, b: 2 }) === 'object') - assert(type(true) === 'boolean') - assert(type(null) === 'null') - assert(type(undefined) === 'null') - }) - await t.step('min function', () => { - const min = expressionFunctions.min - assert(min([1, 2, 3]) === 1) - assert(min([3, 2, 1]) === 1) - assert(min([]) === Infinity) - // @ts-ignore - assert(min(['3', '2', '1']) === 1) - // @ts-ignore - assert(min(['3', 'string', '1']) === 1) - // @ts-ignore - assert(min(['3', 'n/a', '1']) === 1) - // @ts-ignore - assert(min(null) === null) - }) - await t.step('max function', () => { - const max = expressionFunctions.max - assert(max([1, 2, 3]) === 3) - assert(max([3, 2, 1]) === 3) - assert(max([]) === -Infinity) - // @ts-ignore - assert(max(['3', '2', '1']) === 3) - // @ts-ignore - assert(max(['3', 'string', '1']) === 3) - // @ts-ignore - assert(max(['3', 'n/a', '1']) === 3) - // @ts-ignore - assert(max(null) === null) - }) - await t.step('length function', () => { - const length = expressionFunctions.length - assert(length([1, 2, 3]) === 3) - // Out-of-scope (but permitted) inputs - // @ts-ignore - assert(length('abc') === 3) - // Out-of-scope inputs - // @ts-ignore - assert(length({ a: 1, b: 2 }) === null) - // @ts-ignore - assert(length(true) === null) - // @ts-ignore - assert(length(null) === null) - }) - await t.step('count function', () => { - const count = expressionFunctions.count - assert(count(['a', 'b', 'a', 'b'], 'a') === 2) - assert(count(['a', 'b', 'a', 'b'], 'c') === 0) - }) - - const exists = expressionFunctions.exists.bind(context) - await t.step('exists(..., "dataset") function', () => { - assert(exists([], 'dataset') === 0) - assert( - exists(['sub-01/ses-01/anat/sub-01_ses-01_T1w.nii.gz'], 'dataset') === 1, - ) - assert( - exists( - ['sub-01/ses-01/anat/sub-01_ses-01_T1w.nii.gz', 'T1w.json'], - 'dataset', - ) === 2, - ) - }) - await t.step('exists(..., "subject") function', () => { - assert(exists([], 'subject') === 0) - assert(exists(['ses-01/anat/sub-01_ses-01_T1w.nii.gz'], 'subject') === 1) - assert( - exists( - ['ses-01/anat/sub-01_ses-01_T1w.nii.gz', 'T1w.json'], - 'subject', - ) === 1, - ) - }) - await t.step('exists(..., "file") function', () => { - assert(exists([], 'file') === 0) - assert(exists(['sub-01_ses-01_T1w.nii.gz'], 'file') === 1) - assert(exists(['sub-01_ses-01_T1w.nii.gz', 'sub-01_ses-01_T1w.json'], 'file') === 2) - assert(exists(['sub-01_ses-01_T1w.nii.gz', 'ses-01_T1w.json'], 'file') === 1) - }) - await t.step('exists(..., "stimuli") function', () => { - assert(exists([], 'stimuli') === 0) - assert(exists(['stimfile1.png'], 'stimuli') === 1) - assert(exists(['stimfile1.png', 'stimfile2.png'], 'stimuli') === 2) - assert(exists(['X.png', 'Y.png'], 'stimuli') === 0) - }) - await t.step('exists(..., "bids-uri") function', () => { - assert(exists([], 'subject') === 0) - assert( - exists( - ['bids::sub-01/ses-01/anat/sub-01_ses-01_T1w.nii.gz'], - 'bids-uri', - ) === 1, - ) - assert( - exists( - ['bids::sub-01/ses-01/anat/sub-01_ses-01_T1w.nii.gz', 'bids::T1w.json'], - 'bids-uri', - ) === 2, - ) - // Not yet implemented; currently returns length of array - // assert(exists(['bids::sub-01/ses-01/anat/sub-01_ses-01_T1w.nii.gz', 'bids::T2w.json'], 'bids-uri') === 1) - }) - - await t.step('substr function', () => { - const substr = expressionFunctions.substr - assert(substr('abc', 0, 1) === 'a') - assert(substr('abc', 1, 2) === 'b') - assert(substr('abc', 2, 3) === 'c') - assert(substr('abc', 3, 4) === '') - assert(substr('abc', 0, 4) === 'abc') - // @ts-ignore - assert(substr(null, 0, 1) === null) - // @ts-ignore - assert(substr('abc', null, 1) === null) - // @ts-ignore - assert(substr('abc', 0, null) === null) - }) - await t.step('sorted(..., "numeric") function', () => { - const sorted = expressionFunctions.sorted - const array_equal = (a: any[], b: any[]) => - a.length === b.length && a.every((v, i) => v === b[i]) - assert(array_equal(sorted([3, 2, 1], 'numeric'), [1, 2, 3])) - assert(array_equal(sorted([1, 2, 3], 'numeric'), [1, 2, 3])) - assert(array_equal(sorted(['3', '2', '1'], 'numeric'), ['1', '2', '3'])) - assert(array_equal(sorted(['1', '2', '3'], 'numeric'), ['1', '2', '3'])) - assert(array_equal(sorted([], 'numeric'), [])) - assert(array_equal(sorted([5, 25, 125, 625], 'numeric'), [5, 25, 125, 625])) - assert(array_equal(sorted([125, 25, 5, 625], 'numeric'), [5, 25, 125, 625])) - assert( - array_equal(sorted(['-2', '-1', '0', '1', '2'], 'numeric'), ['-2', '-1', '0', '1', '2']), - ) - }) - await t.step('sorted(..., "lexical") function', () => { - const sorted = expressionFunctions.sorted - const array_equal = (a: any[], b: any[]) => - a.length === b.length && a.every((v, i) => v === b[i]) - assert(array_equal(sorted([3, 2, 1], 'lexical'), [1, 2, 3])) - assert(array_equal(sorted([1, 2, 3], 'lexical'), [1, 2, 3])) - assert(array_equal(sorted(['3', '2', '1'], 'lexical'), ['1', '2', '3'])) - assert(array_equal(sorted(['1', '2', '3'], 'lexical'), ['1', '2', '3'])) - assert(array_equal(sorted([], 'lexical'), [])) - assert( - array_equal(sorted(['5', '25', '125', '625'], 'lexical'), [ - '125', - '25', - '5', - '625', - ]), - ) - assert( - array_equal(sorted(['125', '25', '5', '625'], 'lexical'), [ - '125', - '25', - '5', - '625', - ]), - ) - }) - await t.step('sorted(..., "auto") function', () => { - const sorted = expressionFunctions.sorted - const array_equal = (a: any[], b: any[]) => - a.length === b.length && a.every((v, i) => v === b[i]) - assert(array_equal(sorted([125, 25, 5, 625], 'auto'), [5, 25, 125, 625])) - assert( - array_equal(sorted(['5', '25', '125', '625'], 'auto'), [ - '125', - '25', - '5', - '625', - ]), - ) - }) - await t.step('allequal function', () => { - const allequal = expressionFunctions.allequal - assert(allequal([1, 2, 1], [1, 2, 1]) === true) - assert(allequal([1, 2, 1], [1, 2, 3]) === false) - assert(allequal(['a', 'b', 'a'], ['a', 'b', 'a']) === true) - assert(allequal(['a', 'b', 'a'], ['a', 'b', 'c']) === false) - assert(allequal([1, 2, 1], [1, 2, 1, 2]) === false) - assert(allequal([1, 2, 1, 2], [1, 2, 1]) === false) - }) -}) diff --git a/bids-validator/src/schema/expressionLanguage.ts b/bids-validator/src/schema/expressionLanguage.ts deleted file mode 100644 index ee399b356..000000000 --- a/bids-validator/src/schema/expressionLanguage.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { BIDSContext } from './context.ts' - -function exists(this: BIDSContext, list: string[], rule: string = 'dataset'): number { - if (list == null) { - return 0 - } - - const prefix: string[] = [] - const fileTree = rule == 'file' ? this.file.parent : this.dataset.tree - - // Stimuli and subject-relative paths get prefixes - if (rule == 'stimuli') { - prefix.push('stimuli') - } else if (rule == 'subject') { - prefix.push('sub-' + this.entities.sub) - } - - if (!Array.isArray(list)) { - list = [list] - } - if (rule == 'bids-uri') { - return list.filter((x) => { - // XXX To implement - if (x.startsWith('bids:')) { - return true - } - return false - }).length - } else { - // dataset, subject and stimuli - return list.filter((x) => { - const parts = prefix.concat(x.split('/')) - return fileTree.contains(parts) - }).length - } -} - -export const expressionFunctions = { - index: (list: T[], item: T): number | null => { - const index = list.indexOf(item) - return index != -1 ? index : null - }, - intersects: (a: T[], b: T[]): boolean => { - if (!Array.isArray(a)) { - a = [a] - } - if (!Array.isArray(b)) { - b = [b] - } - return a.some((x) => b.includes(x)) - }, - match: (target: string, regex: string): boolean => { - const re = RegExp(regex) - return target.match(re) !== null - }, - type: (operand: T): string => { - if (Array.isArray(operand)) { - return 'array' - } - if (typeof operand === 'undefined' || operand === null) { - return 'null' - } - return typeof operand - }, - min: (list: number[]): number | null => { - return list != null ? Math.min(...list.map(Number).filter((x) => !isNaN(x))) : null - }, - max: (list: number[]): number | null => { - return list != null ? Math.max(...list.map(Number).filter((x) => !isNaN(x))) : null - }, - length: (list: T[]): number | null => { - if (Array.isArray(list) || typeof list == 'string') { - return list.length - } - return null - }, - count: (list: T[], val: T): number => { - return list.filter((x) => x === val).length - }, - exists: exists, - substr: (arg: string, start: number, end: number): string | null => { - if (arg == null || start == null || end == null) { - return null - } - return arg.substr(start, end - start) - }, - sorted: (list: T[], method: string = 'auto'): T[] => { - const cmp = { - numeric: (a: T, b: T) => { - return Number(a) - Number(b) - }, - lexical: (a: T, b: T) => { - return String(a).localeCompare(String(b)) - }, - auto: (a: T, b: T) => { - return +(a > b) - +(a < b) - }, - }[method] - return list.toSorted(cmp) - }, - allequal: (a: T[], b: T[]): boolean => { - return (a != null && b != null) && a.length === b.length && a.every((v, i) => v === b[i]) - }, -} diff --git a/bids-validator/src/schema/fixtures.test.ts b/bids-validator/src/schema/fixtures.test.ts deleted file mode 100644 index 9f5f7b8f9..000000000 --- a/bids-validator/src/schema/fixtures.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { BIDSFile, FileTree } from '../types/filetree.ts' -import { pathsToTree } from '../files/filetree.ts' -import { nullReadBytes } from '../tests/nullReadBytes.ts' - -const anatJson = JSON.stringify({ - rootOverwrite: 'anat', - subOverwrite: 'anat', - anatValue: 'anat', -}) -const subjectJson = JSON.stringify({ subOverwrite: 'subject', subValue: 'subject' }) -const rootJson = JSON.stringify({ rootOverwrite: 'root', rootValue: 'root' }) - -function readBytes(json: string) { - return (size: number) => Promise.resolve(new TextEncoder().encode(json)) -} - -export const rootFileTree = pathsToTree([ - '/dataset_description.json', - '/T1w.json', - '/sub-01/ses-01_T1w.json', - '/sub-01/ses-01/anat/sub-01_ses-01_T1w.nii.gz', - '/sub-01/ses-01/anat/sub-01_ses-01_T1w.json', - ...[...Array(10).keys()].map((i) => `/stimuli/stimfile${i}.png`), -]) - -const rootJSONFile = rootFileTree.get('T1w.json') as BIDSFile -rootJSONFile.readBytes = readBytes(rootJson) - -const subjectFileTree = rootFileTree.get('sub-01') as FileTree -const subjectJSONFile = subjectFileTree.files[0] as BIDSFile -subjectJSONFile.readBytes = readBytes(subjectJson) - -const anatFileTree = subjectFileTree.directories[0].directories[0] as FileTree - -export const dataFile = anatFileTree.get('sub-01_ses-01_T1w.nii.gz') as BIDSFile -const anatJSONFile = anatFileTree.get('sub-01_ses-01_T1w.json') as BIDSFile -anatJSONFile.readBytes = (size: number) => Promise.resolve(new TextEncoder().encode(anatJson)) diff --git a/bids-validator/src/schema/modalities.ts b/bids-validator/src/schema/modalities.ts deleted file mode 100644 index 53baca638..000000000 --- a/bids-validator/src/schema/modalities.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Schema } from '../types/schema.ts' - -export function lookupModality(schema: Schema, datatype: string): string { - const modalities = schema.rules.modalities as Record - const datatypes = Object.keys(modalities).filter((key: string) => { - modalities[key].datatypes.includes(datatype) - }) - if (datatypes.length === 1) { - return datatypes[0] - } else if (datatypes.length === 0) { - return '' - } else { - // what if multiple modalites are found? - return '' - } -} diff --git a/bids-validator/src/schema/tables.test.ts b/bids-validator/src/schema/tables.test.ts deleted file mode 100644 index 433538b09..000000000 --- a/bids-validator/src/schema/tables.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -// @ts-nocheck -import { assertEquals } from '@std/assert' -import { loadSchema } from '../setup/loadSchema.ts' -import { - evalAdditionalColumns, - evalColumns, - evalIndexColumns, - evalInitialColumns, -} from './tables.ts' -import { DatasetIssues } from '../issues/datasetIssues.ts' - -const schemaDefs = { - rules: { - tabular_data: { - modality_agnostic: { - Scans: { - selectors: ['suffix == "scans"', 'extension == ".tsv"'], - initial_columns: ['filename'], - columns: { - filename: { - level: 'required', - description_addendum: 'There MUST be exactly one row for each file.', - }, - acq_time__scans: 'optional', - }, - index_columns: ['filename'], - additional_columns: 'allowed', - }, - }, - made_up: { - MadeUp: { - columns: { - onset: 'required', - strain_rrid: 'optional', - }, - additional_columns: 'not_allowed', - }, - }, - }, - }, -} - -Deno.test('tables eval* tests', async (t) => { - const schema = await loadSchema() - - await t.step('check invalid datetime (scans.tsv:acq_time)', () => { - const context = { - path: '/sub-01/sub-01_scans.tsv', - extension: '.tsv', - sidecar: {}, - columns: { - filename: ['func/sub-01_task-rest_bold.nii.gz'], - acq_time: ['1900-01-01T00:00:78'], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.modality_agnostic.Scans - evalColumns(rule, context, schema, 'rules.tabular_data.modality_agnostic.Scans') - assertEquals( - context.dataset.issues.get({ code: 'TSV_VALUE_INCORRECT_TYPE_NONREQUIRED' }).length, - 1, - ) - }) - - await t.step('check formatless column', () => { - const context = { - path: '/sub-01/sub-01_something.tsv', - extension: '.tsv', - sidecar: {}, - columns: { - onset: ['1', '2', 'not a number'], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.made_up.MadeUp - evalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals(context.dataset.issues.get({ code: 'TSV_VALUE_INCORRECT_TYPE' }).length, 1) - }) - - await t.step('verify n/a is allowed', () => { - const context = { - path: '/sub-01/sub-01_something.tsv', - extension: '.tsv', - sidecar: {}, - columns: { - onset: ['1', '2', 'n/a'], - strain_rrid: ['RRID:SCR_012345', 'RRID:SCR_012345', 'n/a'], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.made_up.MadeUp - evalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals(context.dataset.issues.size, 0) - }) - - await t.step('verify column ordering', () => { - const context = { - path: '/sub-01/sub-01_scans.tsv', - extension: '.tsv', - sidecar: {}, - columns: { - onset: ['1900-01-01:00:00'], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.modality_agnostic.Scans - evalInitialColumns(rule, context, schema, 'rules.tabular_data.modality_agnostic.Scans') - assertEquals( - context.dataset.issues.get({ code: 'TSV_COLUMN_MISSING' }).length, - 1, - ) - assertEquals( - context.dataset.issues.get({ code: 'TSV_COLUMN_ORDER_INCORRECT' }).length, - 0, - ) - - context.columns['filename'] = ['func/sub-01_task-rest_bold.nii.gz'] - evalInitialColumns(rule, context, schema, 'rules.tabular_data.modality_agnostic.Scans') - assertEquals( - context.dataset.issues.get({ code: 'TSV_COLUMN_ORDER_INCORRECT' }).length, - 1, - ) - }) - - await t.step('verify column index', () => { - const context = { - path: '/sub-01/sub-01_scans.tsv', - extension: '.tsv', - sidecar: {}, - columns: { - onset: ['1900-01-01:00:00', '1900-01-01:00:00'], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.modality_agnostic.Scans - evalIndexColumns(rule, context, schema, 'rules.tabular_data.modality_agnostic.Scans') - assertEquals( - context.dataset.issues.get({ code: 'TSV_COLUMN_MISSING' }).length, - 1, - ) - context.columns['filename'] = [ - 'func/sub-01_task-rest_bold.nii.gz', - 'func/sub-01_task-rest_bold.nii.gz', - ] - evalIndexColumns(rule, context, schema, 'rules.tabular_data.modality_agnostic.Scans') - assertEquals( - context.dataset.issues.get({ code: 'TSV_INDEX_VALUE_NOT_UNIQUE' }).length, - 1, - ) - }) - - await t.step('verify not allowed additional columns', () => { - const context = { - path: '/sub-01/sub-01_scans.tsv', - extension: '.tsv', - sidecar: {}, - columns: { - onset: ['1', '2', 'n/a'], - strain_rrid: ['RRID:SCR_012345', 'RRID:SCR_012345', 'n/a'], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.made_up.MadeUp - evalAdditionalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals(context.dataset.issues.size, 0) - - context.columns['extra'] = [1, 2, 3] - evalAdditionalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals( - context.dataset.issues.get({ code: 'TSV_ADDITIONAL_COLUMNS_NOT_ALLOWED' }).length, - 1, - ) - }) - await t.step('verify allowed and allowed_if_defined additional columns', () => { - const context = { - path: '/sub-01/sub-01_scans.tsv', - extension: '.tsv', - sidecar: { 'extra': { 'description': 'a fun and whimsical extra column' } }, - columns: { - onset: ['1', '2', 'n/a'], - strain_rrid: ['RRID:SCR_012345', 'RRID:SCR_012345', 'n/a'], - extra: [1, 2, 3], - }, - dataset: { issues: new DatasetIssues() }, - } - const rule = schemaDefs.rules.tabular_data.made_up.MadeUp - rule.additional_columns = 'allowed_if_defined' - evalAdditionalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals(context.dataset.issues.size, 0) - - context['sidecar'] = {} - evalAdditionalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals( - context.dataset.issues.get({ code: 'TSV_ADDITIONAL_COLUMNS_MUST_DEFINE' }).length, - 1, - ) - - rule.additional_columns = 'allowed' - evalAdditionalColumns(rule, context, schema, 'rules.tabular_data.made_up.MadeUp') - assertEquals( - context.dataset.issues.get({ code: 'TSV_ADDITIONAL_COLUMNS_UNDEFINED' }).length, - 1, - ) - }) -}) diff --git a/bids-validator/src/schema/tables.ts b/bids-validator/src/schema/tables.ts deleted file mode 100644 index acf88a8c3..000000000 --- a/bids-validator/src/schema/tables.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { GenericRule, GenericSchema, SchemaType, SchemaTypeLike } from '../types/schema.ts' -import { BIDSContext } from './context.ts' - -interface ColumnDefinition { - Levels?: Record - Units?: string -} - -/** - * schema.formats contains named types with patterns. Many entries in - * schema.objects have a format to constrain its possible values. Presently - * this is written with tsv's in mind. The blanket n/a pass may be inappropriate - * for other type checks. filenameValidate predates this but does similar type - * checking for entities. - */ -function schemaObjectTypeCheck( - schemaObject: SchemaTypeLike, - value: string, - schema: GenericSchema, -): boolean { - // always allow n/a? - if (value === 'n/a') { - return true - } - - if ('anyOf' in schemaObject) { - return schemaObject.anyOf.some((x) => schemaObjectTypeCheck(x, value, schema)) - } - if ('enum' in schemaObject && schemaObject.enum) { - return schemaObject.enum.some((x) => x === value) - } - - const format = schemaObject.format - // @ts-expect-error - ? schema.objects.formats[schemaObject.format] - // @ts-expect-error - : schema.objects.formats[schemaObject.type] - const re = new RegExp(`^${format.pattern}$`) - return re.test(value) -} - -/** - * Checks user supplied type information from a sidecar against tsv column value. - */ -function sidecarDefinedTypeCheck( - rule: ColumnDefinition, - value: string, - schema: GenericSchema, -): boolean { - if (typeof rule?.Levels === 'object') { - return value == 'n/a' || value in rule['Levels'] - } else if ('Units' in rule) { - return schemaObjectTypeCheck({ 'type': 'number' }, value, schema) - } else { - return true - } -} - -/** - * Check whether sidecar and schema definitions are compatible. - */ -function compatibleDefinitions( - rule: ColumnDefinition, - schemaObject: SchemaType, -): boolean { - const schemaLike: { - type?: string - enum?: string[] - unit?: string - } = {} - if (typeof rule?.Levels === 'object') { - schemaLike.enum = [...Object.keys(rule.Levels)] - schemaLike.type = typeof schemaLike.enum[0] - } - if (rule?.Units) { - schemaLike.type = 'number' - schemaLike.unit = rule.Units - } - // Suppose we are overriding the schema with the sidecar... - const effectiveType = schemaLike.type || schemaObject.type - const effectiveEnum = schemaLike.enum || schemaObject.enum - const effectiveUnit = schemaLike.unit || schemaObject.unit - - // Types are compatible if unchanged, or both numeric - const typeCompatible = effectiveType === schemaObject.type || - effectiveType === 'number' && schemaObject.type === 'integer' - // Enums are compatible if the sidecar enum is a subset of the schema enum - const enumCompatible = schemaObject.enum === undefined || - effectiveEnum?.every((x) => schemaObject?.enum?.includes(x)) as boolean - // Units are compatible if the sidecar unit is the same as the schema unit - const unitCompatible = schemaObject.unit === undefined || effectiveUnit === schemaObject.unit - return typeCompatible && enumCompatible && unitCompatible -} - -/** - * Columns in schema rules are assertions about the requirement level of what - * headers should be present in a tsv file. Examples in specification: - * schema/rules/tabular_data/* - * - * For each column in a rule.tabluar_data check we generate an error if the - * column is missing from the tsv and listed as required by the schema, a - * warning if the schema rule is clobbered by the sidecar but shouldn't be. If - * the column is not in the tsv we bail out and move to the next column, - * otherwise we type check each value in the column according to the type - * specified in the schema rule (or sidecar type information if applicable). - */ -export function evalColumns( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -): void { - if (!rule.columns || context.extension !== '.tsv') return - const headers = [...Object.keys(context.columns)] - for (const [ruleHeader, requirement] of Object.entries(rule.columns)) { - // @ts-expect-error - const columnObject: GenericRule = schema.objects.columns[ruleHeader] - if (!('name' in columnObject) || !columnObject['name']) { - return - } - const name = columnObject.name - let typeCheck = (value: string) => - schemaObjectTypeCheck( - columnObject as unknown as SchemaTypeLike, - value, - schema, - ) - const error_code = (requirement != 'required') - ? 'TSV_VALUE_INCORRECT_TYPE_NONREQUIRED' - : 'TSV_VALUE_INCORRECT_TYPE' - let errorObject = columnObject - - if (!headers.includes(name) && requirement === 'required') { - context.dataset.issues.add({ - code: 'TSV_COLUMN_MISSING', - subCode: name, - location: context.path, - rule: schemaPath, - }) - } - - if ('definition' in columnObject) { - typeCheck = (value) => - // @ts-expect-error - sidecarDefinedTypeCheck(columnObject.definition, value, schema) - } - - const inspect = typeof Deno !== 'undefined' - ? Deno.inspect - : (x: any) => JSON.stringify(x, null, 2) - - if ( - name in context.sidecar && context.sidecar[name] && - typeof (context.sidecar[name]) === 'object' - ) { - if ('definition' in columnObject) { - typeCheck = (value) => sidecarDefinedTypeCheck(context.sidecar[name], value, schema) - errorObject = context.sidecar[name] - } else if ( - !compatibleDefinitions(context.sidecar[name], columnObject as unknown as SchemaType) - ) { - context.dataset.issues.add({ - code: 'TSV_COLUMN_TYPE_REDEFINED', - subCode: name, - location: context.path, - issueMessage: `defined in ${context.sidecarKeyOrigin[name]}`, - rule: schemaPath, - }) - } - } - - if (!headers.includes(name)) { - continue - } - - for (const value of context.columns[name] as string[]) { - if (!typeCheck(value)) { - const issueMessage = `'${value}'` + - (value.match(/^\s*(NA|na|nan|NaN|n\/a)\s*$/) ? ", did you mean 'n/a'?" : '') - context.dataset.issues.add({ - code: error_code, - subCode: name, - location: context.path, - rule: schemaPath, - issueMessage, - }) - break - } - } - } -} - -/** - * A small subset of tsv schema rules enforce a specific order of columns. - * No error is currently provided by the rule itself. - */ -export function evalInitialColumns( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -): void { - if ( - !rule?.columns || !rule?.initial_columns || context.extension !== '.tsv' - ) { - return - } - const headers = [...Object.keys(context.columns)] - rule.initial_columns.map((ruleHeader: string, ruleIndex: number) => { - // @ts-expect-error - const ruleHeaderName = schema.objects.columns[ruleHeader].name - const contextIndex = headers.findIndex((x) => x === ruleHeaderName) - if (contextIndex === -1) { - context.dataset.issues.add({ - code: 'TSV_COLUMN_MISSING', - subCode: ruleHeaderName, - location: context.path, - issueMessage: `Column ${ruleIndex} (starting from 0) not found.`, - rule: schemaPath, - }) - } else if (ruleIndex !== contextIndex) { - context.dataset.issues.add({ - code: 'TSV_COLUMN_ORDER_INCORRECT', - subCode: ruleHeaderName, - location: context.path, - issueMessage: `Column ${ruleIndex} (starting from 0) found at index ${contextIndex}.`, - rule: schemaPath, - }) - } - }) -} - -export function evalAdditionalColumns( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -): void { - if (context.extension !== '.tsv') return - const headers = Object.keys(context?.columns) - if (rule.columns) { - const ruleHeadersNames = Object.keys(rule.columns).map( - // @ts-expect-error - (x) => schema.objects.columns[x].name, - ) - let extraCols = headers.filter( - (header) => !ruleHeadersNames.includes(header), - ) - - if (rule.additional_columns?.startsWith('allowed')) { - extraCols = extraCols.filter((header) => !(header in context.sidecar)) - } - const code = rule.additional_columns === 'allowed' - ? 'TSV_ADDITIONAL_COLUMNS_UNDEFINED' - : rule.additional_columns === 'allowed_if_defined' - ? 'TSV_ADDITIONAL_COLUMNS_MUST_DEFINE' - : 'TSV_ADDITIONAL_COLUMNS_NOT_ALLOWED' - const issue = { - code, - location: context.path, - rule: schemaPath, - } - for (const col of extraCols) { - context.dataset.issues.add({ ...issue, subCode: col }) - } - } -} - -export function evalIndexColumns( - rule: GenericRule, - context: BIDSContext, - schema: GenericSchema, - schemaPath: string, -): void { - if ( - !rule?.columns || - !rule?.index_columns || - !rule?.index_columns.length || - context.extension !== '.tsv' - ) { - return - } - const headers = Object.keys(context?.columns) - const uniqueIndexValues = new Set() - const index_columns = rule.index_columns.map((col: string) => { - // @ts-expect-error - return schema.objects.columns[col].name - }) - const missing = index_columns.filter((col: string) => !headers.includes(col)) - for (const col of missing) { - context.dataset.issues.add({ - code: 'TSV_COLUMN_MISSING', - subCode: col, - location: context.path, - rule: schemaPath, - }) - } - - const rowCount = (context.columns[index_columns[0]] as string[])?.length || 0 - for (let i = 0; i < rowCount; i++) { - let indexValue = '' - index_columns.map((col: string) => { - indexValue = indexValue.concat( - (context.columns[col] as string[])?.[i] || '', - ) - }) - if (uniqueIndexValues.has(indexValue)) { - context.dataset.issues.add({ - code: 'TSV_INDEX_VALUE_NOT_UNIQUE', - location: context.path, - issueMessage: `Row: ${i + 2}, Value: ${indexValue}`, - rule: schemaPath, - }) - } else { - uniqueIndexValues.add(indexValue) - } - } -} diff --git a/bids-validator/src/schema/walk.test.ts b/bids-validator/src/schema/walk.test.ts deleted file mode 100644 index cb38cef4e..000000000 --- a/bids-validator/src/schema/walk.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { assert, assertEquals } from '@std/assert' -import { BIDSContext, BIDSContextDataset } from './context.ts' -import { walkFileTree } from './walk.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import { simpleDataset, simpleDatasetFileCount } from '../tests/simple-dataset.ts' - -Deno.test('file tree walking', async (t) => { - await t.step('visits each file and creates a BIDSContext', async () => { - const dsContext = new BIDSContextDataset({ tree: simpleDataset }) - for await (const context of walkFileTree(dsContext)) { - assert( - context instanceof BIDSContext, - 'walk file tree did not return a BIDSContext', - ) - } - }) - await t.step('visits every file expected', async () => { - const dsContext = new BIDSContextDataset({ tree: simpleDataset }) - let accumulator = 0 - for await (const context of walkFileTree(dsContext)) { - assert( - context instanceof BIDSContext, - 'walk file tree did not return a BIDSContext', - ) - accumulator = accumulator + 1 - } - assertEquals( - accumulator, - simpleDatasetFileCount, - 'visited file count does not match expected value', - ) - }) -}) diff --git a/bids-validator/src/schema/walk.ts b/bids-validator/src/schema/walk.ts deleted file mode 100644 index 365e57266..000000000 --- a/bids-validator/src/schema/walk.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { BIDSContext, type BIDSContextDataset } from './context.ts' -import type { BIDSFile, FileTree } from '../types/filetree.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import { loadTSV } from '../files/tsv.ts' - -function* quickWalk(dir: FileTree): Generator { - for (const file of dir.files) { - yield file - } - for (const subdir of dir.directories) { - yield* quickWalk(subdir) - } -} - -const nullFile = { - stream: new ReadableStream(), - text: async () => '', - readBytes: async (size: number, offset?: number) => new Uint8Array(), -} - -function pseudoFile(dir: FileTree): BIDSFile { - return { - name: `${dir.name}/`, - path: `${dir.path}/`, - size: [...quickWalk(dir)].reduce((acc, file) => acc + file.size, 0), - ignored: dir.ignored, - parent: dir.parent as FileTree, - viewed: dir.viewed, - ...nullFile, - } -} - -/** Recursive algorithm for visiting each file in the dataset, creating a context */ -async function* _walkFileTree( - fileTree: FileTree, - dsContext: BIDSContextDataset, -): AsyncIterable { - for (const file of fileTree.files) { - yield new BIDSContext(file, dsContext) - } - for (const dir of fileTree.directories) { - if (dsContext.isPseudoFile(dir)) { - yield new BIDSContext(pseudoFile(dir), dsContext) - } else { - yield* _walkFileTree(dir, dsContext) - } - } - loadTSV.cache.delete(fileTree.path) -} - -/** Walk all files in the dataset and construct a context for each one */ -export async function* walkFileTree( - dsContext: BIDSContextDataset, -): AsyncIterable { - yield* _walkFileTree(dsContext.tree, dsContext) -} diff --git a/bids-validator/src/setup/loadSchema.test.ts b/bids-validator/src/setup/loadSchema.test.ts deleted file mode 100644 index 1dc9b2e63..000000000 --- a/bids-validator/src/setup/loadSchema.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { assert, assertObjectMatch } from '@std/assert' -import { loadSchema } from './loadSchema.ts' - -Deno.test('schema loader', async (t) => { - await t.step('reads in top level files document', async () => { - const schemaDefs = await loadSchema() - // Look for some stable fields in top level files - if ( - typeof schemaDefs.rules.files.common === 'object' && - schemaDefs.rules.files.common.core !== null - ) { - const top_level = schemaDefs.rules.files.common.core as Record< - string, - any - > - if (top_level.hasOwnProperty('README')) { - assertObjectMatch(top_level.README, { - level: 'recommended', - stem: 'README', - extensions: ['', '.md', '.rst', '.txt'], - }) - } - } else { - assert(false, 'failed to test schema defs') - } - }) - await t.step('loads all schema files', async () => { - const schemaDefs = await loadSchema() - if ( - !(typeof schemaDefs.objects === 'object') || - !(typeof schemaDefs.rules === 'object') - ) { - assert(false, 'failed to load objects/rules') - } - }) -}) diff --git a/bids-validator/src/setup/loadSchema.ts b/bids-validator/src/setup/loadSchema.ts deleted file mode 100644 index d8b45f56f..000000000 --- a/bids-validator/src/setup/loadSchema.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Schema } from '../types/schema.ts' -import { objectPathHandler } from '../utils/objectPathHandler.ts' -import { schema as schemaDefault } from '@bids/schema' -import { setCustomMetadataFormats } from '../validators/json.ts' - -/** - * Load the schema from the specification - * - * version is ignored when the network cannot be accessed - */ -export async function loadSchema(version?: string): Promise { - let schemaUrl = version - const bidsSchema = typeof Deno !== 'undefined' ? Deno.env.get('BIDS_SCHEMA') : undefined - if (bidsSchema !== undefined) { - schemaUrl = bidsSchema - } else if (version?.match(/^(v\d+\.\d+\.\d+|stable|latest)$/)) { - schemaUrl = `https://bids-specification.readthedocs.io/en/${version}/schema.json` - } - - let schema: Schema = new Proxy( - schemaDefault as object, - objectPathHandler, - ) as Schema - - if (schemaUrl !== undefined) { - try { - const jsonResponse = await fetch(schemaUrl) - const jsonData = await jsonResponse.json() - schema = new Proxy( - jsonData as object, - objectPathHandler, - ) as Schema - } catch (error) { - // No network access or other errors - console.error(error) - console.error( - `Warning, could not load schema from ${schemaUrl}, falling back to internal version`, - ) - } - } - setCustomMetadataFormats(schema) - return schema -} diff --git a/bids-validator/src/setup/options.test.ts b/bids-validator/src/setup/options.test.ts deleted file mode 100644 index d7ef28792..000000000 --- a/bids-validator/src/setup/options.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { assertEquals } from '@std/assert' -import { parseOptions } from './options.ts' - -Deno.test('options parsing', async (t) => { - await t.step('verify basic arguments work', async () => { - const options = await parseOptions(['my_dataset', '--json']) - assertEquals(options, { - datasetPath: 'my_dataset', - debug: 'ERROR', - json: true, - color: false, - blacklistModalities: [], - }) - }) -}) diff --git a/bids-validator/src/setup/options.ts b/bids-validator/src/setup/options.ts deleted file mode 100644 index 76aa10f1d..000000000 --- a/bids-validator/src/setup/options.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { LogLevelNames } from '@std/log' -import type { LevelName } from '@std/log' -import { Command, EnumType } from '@cliffy/command' -import { getVersion } from '../version.ts' -import type { Issue, Severity } from '../types/issues.ts' -import { schema } from '@bids/schema' - -/** - * BIDS Validator config file object definition - */ -export type Config = { - [key in Severity]?: Partial[] -} - -/** - * BIDS Validator options object definition - */ -export type ValidatorOptions = { - datasetPath: string - schema?: string - config?: string - json?: boolean - verbose?: boolean - ignoreNiftiHeaders?: boolean - ignoreWarnings?: boolean - filenameMode?: boolean - debug: LevelName - color?: boolean - recursive?: boolean - outfile?: string - blacklistModalities: string[] -} - -const modalityType = new EnumType( - Object.keys(schema.rules.modalities) -) - -/** Extendable Cliffy Command with built in BIDS validator options */ -export const validateCommand: Command = new Command() - .name('bids-validator') - .type('debugLevel', new EnumType(LogLevelNames)) - .description( - 'This tool checks if a dataset in a given directory is compatible with the Brain Imaging Data Structure specification. To learn more about Brain Imaging Data Structure visit http://bids.neuroimaging.io', - ) - .arguments('') - .option('--json', 'Output machine readable JSON') - .option( - '-s, --schema ', - 'Specify a schema version to use for validation', - ) - .option('-c, --config ', 'Path to a JSON configuration file') - .option('-v, --verbose', 'Log more extensive information about issues') - .option('--ignoreWarnings', 'Disregard non-critical issues') - .option( - '--ignoreNiftiHeaders', - 'Disregard NIfTI header content during validation', - ) - .option('--debug ', 'Enable debug output', { - default: 'ERROR', - }) - .option( - '--filenameMode', - 'Enable filename checks for newline separated filenames read from stdin', - ) - .type('modality', modalityType) - .option( - '--blacklistModalities <...modalities:modality>', - 'Array of modalities to error on if detected.', - { default: [] as string[] }, - ) - .option( - '-r, --recursive', - 'Validate datasets found in derivatives directories in addition to root dataset', - ) - .option( - '-o, --outfile ', - 'File to write validation results to.', - ) - -// Disabling color output is only available in Deno -if (typeof Deno !== 'undefined') { - validateCommand - .option( - '--color, --no-color [color:boolean]', - 'Enable/disable color output (defaults to detected support)', - { - default: Deno.stdout.isTerminal() || !!Deno.env.get('FORCE_COLOR'), - }, - ) -} - -/** - * Parse command line options and return a ValidatorOptions config - * @param argumentOverride Override the arguments instead of using Deno.args - */ -export async function parseOptions( - argumentOverride: string[] = Deno.args, -): Promise { - const version = await getVersion() - const { args, options } = await validateCommand.version(version) - .parse(argumentOverride) - return { - datasetPath: args[0], - ...options, - debug: options.debug as LevelName, - } -} diff --git a/bids-validator/src/setup/requestPermissions.ts b/bids-validator/src/setup/requestPermissions.ts deleted file mode 100644 index ef9fc7bc2..000000000 --- a/bids-validator/src/setup/requestPermissions.ts +++ /dev/null @@ -1,27 +0,0 @@ -const globalRead = { name: 'read' } as const -const globalEnv = { name: 'env' } as const - -/** - * Request / query a PermissionDescriptor - */ -async function requestPermission( - permission: Deno.PermissionDescriptor, -): Promise { - const status = await Deno.permissions.request(permission) - - if (status.state === 'granted') { - return true - } else { - return false - } -} - -/** - * Request read permissions - */ -export const requestReadPermission = () => requestPermission(globalRead) - -/** - * Request environment variable permissions - */ -export const requestEnvPermission = () => requestPermission(globalEnv) diff --git a/bids-validator/src/summary/collectSubjectMetadata.ts b/bids-validator/src/summary/collectSubjectMetadata.ts deleted file mode 100644 index 89ee9041b..000000000 --- a/bids-validator/src/summary/collectSubjectMetadata.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { SubjectMetadata } from '../types/validation-result.ts' -const PARTICIPANT_ID = 'participantId' -/** - * Go from tsv format string with participant_id as a required header to array of form - * [ - * { - * participantId: 'participant_id_1' - * foo: 'x', - * ... - * }, - * { - * participantId: 'participant_id_2' - * foo: 'y', - * ... - * } - * ... - * ] - * - * returns null if participant_id is not a header or file contents do not exist - * @param {string} participantsTsvContent - */ -export const collectSubjectMetadata = ( - participantsTsvContent: string, -): SubjectMetadata[] => { - if (!participantsTsvContent) { - return [] - } - - const contentTable = participantsTsvContent - .split(/\r?\n/) - .filter((row) => row !== '') - .map((row) => row.split('\t')) - const [snakeCaseHeaders, ...subjectData] = contentTable - const headers = snakeCaseHeaders.map((header) => - header === 'participant_id' ? PARTICIPANT_ID : header - ) - const targetKeys = [PARTICIPANT_ID, 'age', 'sex', 'group'] - .map((key) => ({ - key, - index: headers.findIndex((targetKey) => targetKey === key), - })) - .filter(({ index }) => index !== -1) - const participantIdKey = targetKeys.find(({ key }) => key === PARTICIPANT_ID) - const ageKey = targetKeys.find(({ key }) => key === 'age') - if (participantIdKey === undefined) return [] as SubjectMetadata[] - else { - return subjectData - .map((data) => { - // this first map is for transforming any data coming out of participants.tsv: - // strip subject ids to match metadata.subjects: 'sub-01' -> '01' - data[participantIdKey.index] = data[participantIdKey.index].replace( - /^sub-/, - '', - ) - // make age an integer - // @ts-expect-error - if (ageKey) data[ageKey.index] = parseInt(data[ageKey.index]) - return data - }) - .map((data) => - //extract all target metadata for each subject - targetKeys.reduce( - (subject, { key, index }) => ({ - ...subject, - [key]: data[index], - }), - {}, - ) - ) as SubjectMetadata[] - } -} diff --git a/bids-validator/src/summary/summary.test.ts b/bids-validator/src/summary/summary.test.ts deleted file mode 100644 index 6d10fa287..000000000 --- a/bids-validator/src/summary/summary.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { computeModalities, modalityPrettyLookup, Summary } from './summary.ts' -import { assertEquals, type assertObjectMatch } from '@std/assert' - -Deno.test('Summary class and helper functions', async (t) => { - await t.step('Constructor succeeds', () => { - new Summary() - }) - await t.step('computeModalities properly sorts modality counts', () => { - const modalitiesIn = { eeg: 5, pet: 6, mri: 6, ieeg: 6 } - const modalitiesOut = ['pet', 'ieeg', 'mri', 'eeg'].map( - (x) => modalityPrettyLookup[x], - ) - assertEquals(computeModalities(modalitiesIn), modalitiesOut) - }) -}) diff --git a/bids-validator/src/summary/summary.ts b/bids-validator/src/summary/summary.ts deleted file mode 100644 index 0c34266b9..000000000 --- a/bids-validator/src/summary/summary.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { collectSubjectMetadata } from './collectSubjectMetadata.ts' -import type { SubjectMetadata, SummaryOutput } from '../types/validation-result.ts' -import type { BIDSContext } from '../schema/context.ts' - -export const modalityPrettyLookup: Record = { - mri: 'MRI', - pet: 'PET', - meg: 'MEG', - eeg: 'EEG', - ieeg: 'iEEG', - micro: 'Microscopy', -} - -const secondaryLookup: Record = { - dwi: 'MRI_Diffusion', - anat: 'MRI_Structural', - func: 'MRI_Functional', - perf: 'MRI_Perfusion', -} - -export function computeModalities( - modalities: Record, -): string[] { - // Order by matching file count - const nonZero = Object.keys(modalities).filter((a) => modalities[a] !== 0) - if (nonZero.length === 0) { - return [] - } - const sortedModalities = nonZero.sort((a, b) => { - if (modalities[b] === modalities[a]) { - // On a tie, hand it to the non-MRI modality - if (b === 'mri') { - return -1 - } else { - return 0 - } - } - return modalities[b] - modalities[a] - }) - return sortedModalities.map((mod) => - mod in modalityPrettyLookup ? modalityPrettyLookup[mod] : mod - ) -} - -function computeSecondaryModalities( - secondary: Record, -): string[] { - const nonZeroSecondary = Object.keys(secondary).filter( - (a) => secondary[a] !== 0, - ) - const sortedSecondary = nonZeroSecondary.sort( - (a, b) => secondary[b] - secondary[a], - ) - return sortedSecondary -} - -export class Summary { - sessions: Set - subjects: Set - subjectMetadata: SubjectMetadata[] - tasks: Set - totalFiles: number - size: number - dataProcessed: boolean - pet: Record - modalitiesCount: Record - secondaryModalitiesCount: Record - dataTypes: Set - schemaVersion: string - constructor() { - this.dataProcessed = false - this.totalFiles = 0 - this.size = 0 - this.sessions = new Set() - this.subjects = new Set() - this.subjectMetadata = [] - this.tasks = new Set() - this.pet = {} - this.dataTypes = new Set() - this.modalitiesCount = { - mri: 0, - pet: 0, - meg: 0, - eeg: 0, - ieeg: 0, - microscopy: 0, - } - this.secondaryModalitiesCount = { - MRI_Diffusion: 0, - MRI_Structural: 0, - MRI_Functional: 0, - MRI_Perfusion: 0, - PET_Static: 0, - PET_Dynamic: 0, - iEEG_ECoG: 0, - iEEG_SEEG: 0, - } - this.schemaVersion = '' - } - get modalities() { - return computeModalities(this.modalitiesCount) - } - get secondaryModalities() { - return computeSecondaryModalities(this.secondaryModalitiesCount) - } - async update(context: BIDSContext): Promise { - if (context.file.path.startsWith('/derivatives') && !this.dataProcessed) { - return - } - - this.totalFiles++ - this.size += await context.file.size - - if ('sub' in context.entities) { - this.subjects.add(context.entities.sub) - } - if ('ses' in context.entities) { - this.sessions.add(context.entities.ses) - } - - if (context.datatype.length) { - this.dataTypes.add(context.datatype) - } - - if (context.extension === '.json') { - if (typeof context.json === 'object' && 'TaskName' in context.json) { - this.tasks.add(context.json.TaskName as string) - } - } - if (context.modality) { - this.modalitiesCount[context.modality]++ - } - - if (context.datatype in secondaryLookup) { - const key = secondaryLookup[context.datatype] - this.secondaryModalitiesCount[key]++ - } else if (context.datatype === 'pet' && 'rec' in context.entities) { - if (['acstat', 'nacstat'].includes(context.entities.rec)) { - this.secondaryModalitiesCount.PET_Static++ - } else if (['acdyn', 'nacdyn'].includes(context.entities.rec)) { - this.secondaryModalitiesCount.PET_Dynamic++ - } - } - - if (context.file.path.endsWith('participants.tsv')) { - const tsvContents = await context.file.text() - this.subjectMetadata = collectSubjectMetadata(tsvContents) - } - } - - formatOutput(): SummaryOutput { - return { - sessions: Array.from(this.sessions), - subjects: Array.from(this.subjects), - subjectMetadata: this.subjectMetadata, - tasks: Array.from(this.tasks), - modalities: this.modalities, - secondaryModalities: this.secondaryModalities, - totalFiles: this.totalFiles, - size: this.size, - dataProcessed: this.dataProcessed, - pet: this.pet, - dataTypes: Array.from(this.dataTypes), - schemaVersion: this.schemaVersion, - } - } -} diff --git a/bids-validator/src/tests/README.md b/bids-validator/src/tests/README.md deleted file mode 100644 index d5677fadb..000000000 --- a/bids-validator/src/tests/README.md +++ /dev/null @@ -1 +0,0 @@ -Support files for test suite. diff --git a/bids-validator/src/tests/bom-utf16.tsv b/bids-validator/src/tests/bom-utf16.tsv deleted file mode 100644 index f96584918..000000000 Binary files a/bids-validator/src/tests/bom-utf16.tsv and /dev/null differ diff --git a/bids-validator/src/tests/bom-utf8.json b/bids-validator/src/tests/bom-utf8.json deleted file mode 100644 index 732778cd9..000000000 --- a/bids-validator/src/tests/bom-utf8.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "example": "JSON for test suite" -} diff --git a/bids-validator/src/tests/generate-filenames.ts b/bids-validator/src/tests/generate-filenames.ts deleted file mode 100644 index 7e0cd1871..000000000 --- a/bids-validator/src/tests/generate-filenames.ts +++ /dev/null @@ -1,26 +0,0 @@ -function randomString() { - return Math.random().toString(36).substring(6) -} - -function randomEntityString(prefix?: string): string { - if (prefix) { - return `${prefix}-${randomString()}` - } else { - return `${randomString()}-${randomString()}` - } -} - -/** - * Generate random filenames not following entity ordering rules if length > 0 - */ -export function generateBIDSFilename(length = 1, extension = '.json') { - const subject = randomEntityString('sub') - const session = randomEntityString('ses') - const run = randomEntityString('run') - const acquisition = randomEntityString('acq') - const parts = [subject, session, run, acquisition] - for (let n = 0; n < length; n++) { - parts.push(randomEntityString()) - } - return parts.join('_') + extension -} diff --git a/bids-validator/src/tests/local/bids_examples.test.ts b/bids-validator/src/tests/local/bids_examples.test.ts deleted file mode 100644 index 58e3b8ec0..000000000 --- a/bids-validator/src/tests/local/bids_examples.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Deno runtime tests for tests/data/valid_dataset -import { type assert, assertEquals } from '@std/assert' -import { Cell, Row, Table } from '@cliffy/table' -import * as colors from '@std/fmt/colors' -import type { Issue } from '../../types/issues.ts' -import type { DatasetIssues } from '../../issues/datasetIssues.ts' -import { type formatAssertIssue, validatePath } from './common.ts' -import { type Config, parseOptions } from '../../setup/options.ts' - -const options = await parseOptions(['fake_dataset_arg', ...Deno.args]) -options.ignoreNiftiHeaders = true -const config: Config = { 'ignore': [{ code: 'EMPTY_FILE' }] } - -let header: string[] = ['issue key', 'filename', 'schema path'] -header = header.map((x) => colors.magenta(x)) - -const errors: Row[] = [] -function formatBEIssue(issue: Issue) { - let code = issue.code - if ('subCode' in issue && issue.subCode) { - code = `${code} - ${issue.subCode}` - } - errors.push( - Row.from([ - colors.red(code), - issue.location, - issue.issueMessage, - ]), - ) -} - -Deno.test('validate bids-examples', async (t) => { - const prefix = 'tests/data/bids-examples' - const dirEntries = Array.from(Deno.readDirSync(prefix)) - - for (const dirEntry of dirEntries.sort((a, b) => a.name.localeCompare(b.name))) { - if (!dirEntry.isDirectory || dirEntry.name.startsWith('.')) { - continue - } - const path = `${prefix}/${dirEntry.name}` - - try { - if (Deno.statSync(`${path}/.SKIP_VALIDATION`).isFile) { - continue - } - } catch (e) {} - const { tree, result } = await validatePath(t, path, options, config) - const dsIssues = result.issues.filter({ 'severity': 'error' }) - await t.step(`${path} has no issues`, () => { - assertEquals(dsIssues.size, 0) - }) - if (dsIssues.size === 0) { - continue - } - - errors.push( - Row.from([ - new Cell(colors.cyan(dirEntry.name)).colSpan(4), - undefined, - undefined, - undefined, - ]).border(true), - ) - dsIssues.issues.map((x) => formatBEIssue(x)) - } - const table = new Table() - .header(header) - .body(errors) - .border(false) - .padding(1) - .indent(2) - .maxColWidth(40) - .toString() - console.log(table) -}) diff --git a/bids-validator/src/tests/local/common.ts b/bids-validator/src/tests/local/common.ts deleted file mode 100644 index 87e1fbb81..000000000 --- a/bids-validator/src/tests/local/common.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { readFileTree } from '../../files/deno.ts' -import { FileTree } from '../../types/filetree.ts' -import { validate } from '../../validators/bids.ts' -import type { ValidationResult } from '../../types/validation-result.ts' -import type { Issue } from '../../types/issues.ts' -import { DatasetIssues } from '../../issues/datasetIssues.ts' -import { Summary } from '../../summary/summary.ts' -import { type Config, parseOptions, type ValidatorOptions } from '../../setup/options.ts' - -export async function validatePath( - t: Deno.TestContext, - path: string, - options: Partial = {}, - config: Config = {}, -): Promise<{ tree: FileTree; result: ValidationResult }> { - let tree: FileTree = new FileTree('', '') - const summary = new Summary() - let result: ValidationResult = { - issues: new DatasetIssues(), - summary: summary.formatOutput(), - } - - await t.step('file tree is read', async () => { - tree = await readFileTree(path) - }) - - await t.step('completes validation', async () => { - result = await validate(tree, { - ...(await parseOptions([path])), - ...options, - }, config) - }) - - return { tree, result } -} - -export function formatAssertIssue(message: string, issue?: Issue[]) { - if (issue && issue.length) { - return `${message}\n${Deno.inspect(issue[0], { depth: 8, colors: true })}` - } else { - return `${message}\nAsserted issue is undefined` - } -} diff --git a/bids-validator/src/tests/local/derivatives.test.ts b/bids-validator/src/tests/local/derivatives.test.ts deleted file mode 100644 index 92a113433..000000000 --- a/bids-validator/src/tests/local/derivatives.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { assert } from '@std/assert' -import { validatePath } from './common.ts' -import { parseOptions } from '../../setup/options.ts' - -Deno.test('recursive option works as expected', async (t) => { - const path = 'tests/data/bids-examples/qmri_mp2rage/' - let options = await parseOptions(['fake_dataset_arg', ...Deno.args]) - let result = await validatePath(t, path, options) - assert(!Object.hasOwn(result.result, 'derivativesSummary')) - options = await parseOptions(['fake_dataset_arg', ...Deno.args, '-r']) - result = await validatePath(t, path, options) - assert(Object.hasOwn(result.result, 'derivativesSummary')) -}) diff --git a/bids-validator/src/tests/local/empty_files.test.ts b/bids-validator/src/tests/local/empty_files.test.ts deleted file mode 100644 index c2f74f1bc..000000000 --- a/bids-validator/src/tests/local/empty_files.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Deno runtime tests for tests/data/empty_files -import { type assert, assertEquals, type assertObjectMatch } from '@std/assert' -import { formatAssertIssue, validatePath } from './common.ts' - -const PATH = 'tests/data/empty_files' - -/** - * Contains stripped down CTF format dataset: Both, BadChannels and - * bad.segments files can be empty and still valid. Everything else must - * not be empty. - */ -Deno.test('empty_files dataset', async (t) => { - const { tree, result } = await validatePath(t, PATH) - - await t.step('correctly ignores .bidsignore files', () => { - assertEquals( - result.issues.get({ code: 'NOT_INCLUDED' }).length, - 0, - formatAssertIssue( - 'NOT_INCLUDED should not be present', - result.issues.get({ code: 'NOT_INCLUDED' }), - ), - ) - }) - - // *.meg4 and BadChannels files are empty. But only *.meg4 is an issue - /* - * https://github.com/bids-standard/bids-validator/issues/1862 - * Commented out meat of tests, and updated first test to expect no issues. - */ - await t.step( - 'EMPTY_FILES error is thrown for only sub-0001_task-AEF_run-01_meg.meg4', - () => { - const issues = result.issues.get({ code: 'EMPTY_FILE' }) - assertEquals( - issues.length, - 1, - 'sub-0001_task-AEF_run-01_meg.ds/ is empty but not present in EMPTY_FILE issue', - ) - assertEquals(issues[0].location, '/sub-0001/meg/sub-0001_task-AEF_run-01_meg.ds/') - }, - ) -}) diff --git a/bids-validator/src/tests/local/hed-integration.test.ts b/bids-validator/src/tests/local/hed-integration.test.ts deleted file mode 100644 index 2f1f57d12..000000000 --- a/bids-validator/src/tests/local/hed-integration.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { formatAssertIssue, validatePath } from './common.ts' -import { assert, assertEquals } from '@std/assert' -import { BIDSFileDeno, readFileTree } from '../../files/deno.ts' -import type { DatasetIssues } from '../../issues/datasetIssues.ts' -import { loadSchema } from '../../setup/loadSchema.ts' -import { BIDSContext, BIDSContextDataset } from '../../schema/context.ts' -import type { BIDSFile, FileTree } from '../../types/filetree.ts' -import type { GenericSchema } from '../../types/schema.ts' -import { hedValidate } from '../../validators/hed.ts' - -Deno.test('hed-validator not triggered', async (t) => { - const PATH = 'tests/data/bids-examples/ds003' - const tree = await readFileTree(PATH) - const schema = await loadSchema() - const dsContext = new BIDSContextDataset({ - dataset_description: { - 'HEDVersion': ['bad_version'], - }, - }) - await t.step('detect hed returns false', async () => { - const eventFile = tree.get('sub-01/func/sub-01_task-rhymejudgment_events.tsv') - assert(eventFile !== undefined) - assert(eventFile instanceof BIDSFileDeno) - const context = new BIDSContext(eventFile, dsContext) - await context.asyncLoads() - await hedValidate(schema as unknown as GenericSchema, context) - assert(context.dataset.issues.size === 0) - }) -}) - -Deno.test('hed-validator fails with bad schema version', async (t) => { - const PATH = 'tests/data/bids-examples/eeg_ds003645s_hed_library' - const tree = await readFileTree(PATH) - const schema = await loadSchema() - const dsContext = new BIDSContextDataset({ - dataset_description: { - 'HEDVersion': ['bad_version'], - }, - }) - await t.step('detect hed returns false', async () => { - const eventFile = tree.get('sub-002/eeg/sub-002_task-FacePerception_run-3_events.tsv') - assert(eventFile !== undefined) - assert(eventFile instanceof BIDSFileDeno) - const context = new BIDSContext(eventFile, dsContext) - await context.asyncLoads() - await hedValidate(schema as unknown as GenericSchema, context) - assertEquals(context.dataset.issues.size, 2) - assertEquals(context.dataset.issues.get({ code: 'HED_ERROR' }).length, 2) - }) -}) diff --git a/bids-validator/src/tests/local/valid_dataset.test.ts b/bids-validator/src/tests/local/valid_dataset.test.ts deleted file mode 100644 index cd6a78c3c..000000000 --- a/bids-validator/src/tests/local/valid_dataset.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Deno runtime tests for tests/data/valid_dataset -import { type assert, assertEquals } from '@std/assert' -import { formatAssertIssue, validatePath } from './common.ts' - -const PATH = 'tests/data/valid_dataset' - -Deno.test('valid_dataset dataset', async (t) => { - const { tree, result } = await validatePath(t, PATH) - - await t.step('correctly ignores .bidsignore files', () => { - assertEquals( - result.issues.get({ code: 'NOT_INCLUDED' }).length, - 0, - formatAssertIssue( - 'NOT_INCLUDED should not be present', - result.issues.get({ code: 'NOT_INCLUDED' }), - ), - ) - }) -}) diff --git a/bids-validator/src/tests/local/valid_filenames.test.ts b/bids-validator/src/tests/local/valid_filenames.test.ts deleted file mode 100644 index b40f632df..000000000 --- a/bids-validator/src/tests/local/valid_filenames.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Deno runtime tests for tests/data/valid_filenames -import { assertEquals } from '@std/assert' -import { formatAssertIssue, validatePath } from './common.ts' - -const PATH = 'tests/data/valid_filenames' - -Deno.test('valid_filenames dataset', async (t) => { - const { tree, result } = await validatePath(t, PATH) - - await t.step('correctly ignores .bidsignore files', () => { - assertEquals( - result.issues.get({ code: 'NOT_INCLUDED' }).length, - 0, - formatAssertIssue( - 'NOT_INCLUDED should not be present', - result.issues.get({ code: 'NOT_INCLUDED' }), - ), - ) - }) -}) diff --git a/bids-validator/src/tests/local/valid_headers.test.ts b/bids-validator/src/tests/local/valid_headers.test.ts deleted file mode 100644 index 3c2621407..000000000 --- a/bids-validator/src/tests/local/valid_headers.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Deno runtime tests for tests/data/valid_headers -import { type assert, assertEquals } from '@std/assert' -import { formatAssertIssue, validatePath } from './common.ts' - -const PATH = 'tests/data/valid_headers' - -Deno.test('valid_headers dataset', async (t) => { - const { tree, result } = await validatePath(t, PATH) - - await t.step('correctly ignores .bidsignore files', () => { - assertEquals( - result.issues.get({ code: 'NOT_INCLUDED' }).length, - 0, - formatAssertIssue( - 'NOT_INCLUDED should not be present', - result.issues.get({ code: 'NOT_INCLUDED' }), - ), - ) - }) - - await t.step('summary has correct tasks', () => { - assertEquals(Array.from(result.summary.tasks), ['rhyme judgment']) - }) - - await t.step('summary has correct dataProcessed', () => { - assertEquals(result.summary.dataProcessed, false) - }) - - await t.step('summary has correct modalities', () => { - assertEquals(result.summary.modalities, ['MRI']) - }) - - await t.step('summary has correct totalFiles', () => { - assertEquals(result.summary.totalFiles, 8) - }) - - await t.step('summary has correct subjectMetadata', () => { - assertEquals(result.summary.subjectMetadata[0], { - age: 25, - participantId: '01', - sex: 'M', - }) - }) -}) diff --git a/bids-validator/src/tests/nullReadBytes.ts b/bids-validator/src/tests/nullReadBytes.ts deleted file mode 100644 index 8fd2bcc02..000000000 --- a/bids-validator/src/tests/nullReadBytes.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const nullReadBytes = (size: number, offset = 1024) => { - return Promise.resolve(new Uint8Array()) -} diff --git a/bids-validator/src/tests/schema-expression-language.test.ts b/bids-validator/src/tests/schema-expression-language.test.ts deleted file mode 100644 index b994fcb2e..000000000 --- a/bids-validator/src/tests/schema-expression-language.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { loadSchema } from '../setup/loadSchema.ts' -import { Table } from '@cliffy/table' -import * as colors from '@std/fmt/colors' -import type { BIDSContext } from '../schema/context.ts' -import { type assert, assertEquals } from '@std/assert' -import { evalCheck } from '../schema/applyRules.ts' -import { expressionFunctions } from '../schema/expressionLanguage.ts' - -const schema = await loadSchema() -const pretty_null = (x: string | null): string => (x === null ? 'null' : x) - -const equal = (a: T, b: T): boolean => { - if (Array.isArray(a) && Array.isArray(b)) { - return a.length === b.length && a.every((val, idx) => val === b[idx]) - } - return a === b -} - -Deno.test('validate schema expression tests', async (t) => { - const results: string[][] = [] - const header = ['expression', 'desired', 'actual', 'result'].map((x) => colors.magenta(x)) - for (const test of schema.meta.expression_tests) { - await t.step(`${test.expression} evals to ${test.result}`, () => { - const context = { file: { parent: null }, dataset: { tree: null } } as unknown as BIDSContext - Object.assign(context, expressionFunctions) - // @ts-expect-error - context.exists.bind(context) - const actual_result = evalCheck(test.expression, context) - if (equal(actual_result, test.result)) { - results.push([ - colors.cyan(test.expression), - pretty_null(test.result), - pretty_null(actual_result), - colors.green('pass'), - ]) - } else { - results.push([ - colors.cyan(test.expression), - pretty_null(test.result), - pretty_null(actual_result), - colors.red('fail'), - ]) - } - assertEquals(actual_result, test.result) - }) - } - results.sort((a, b) => { - return a[3].localeCompare(b[3]) - }) - const table = new Table() - .header(header) - .border(false) - .body(results) - .padding(1) - .indent(2) - .maxColWidth(40) - .toString() - console.log(table) -}) diff --git a/bids-validator/src/tests/simple-dataset.ts b/bids-validator/src/tests/simple-dataset.ts deleted file mode 100644 index 60d8243da..000000000 --- a/bids-validator/src/tests/simple-dataset.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { pathsToTree } from '../files/filetree.ts' - -export const simpleDataset = pathsToTree([ - '/dataset_description.json', - '/README', - '/CHANGES', - '/participants.tsv', - '/sub-01/anat/sub-01_T1w.nii.gz', -]) - -export const simpleDatasetFileCount = 5 diff --git a/bids-validator/src/types/check.ts b/bids-validator/src/types/check.ts deleted file mode 100644 index 232de35b4..000000000 --- a/bids-validator/src/types/check.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { GenericSchema } from './schema.ts' -import type { BIDSContext, BIDSContextDataset } from '../schema/context.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' - -/** Function interface for writing a check */ -export type ContextCheckFunction = ( - schema: GenericSchema, - context: BIDSContext, -) => Promise - -/** Function interface for a check of context against a specific rule as accessed by its path in the schema. */ -export type RuleCheckFunction = ( - path: string, - schema: GenericSchema, - context: BIDSContext, -) => void - -export type DSCheckFunction = ( - schema: GenericSchema, - dsContext: BIDSContextDataset, -) => Promise diff --git a/bids-validator/src/types/columns.test.ts b/bids-validator/src/types/columns.test.ts deleted file mode 100644 index e7f0cc570..000000000 --- a/bids-validator/src/types/columns.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { assertEquals } from '@std/assert' -import { ColumnsMap } from './columns.ts' - -Deno.test('ColumnsMap', async (t) => { - await t.step('get is accessible with square bracket notation', () => { - const columns = new ColumnsMap() - columns['a'] = ['0'] - assertEquals(columns['a'], ['0']) - }) - await t.step('preserves insertion order with property access', () => { - const columns = new ColumnsMap() - columns['a'] = ['0'] - columns[2] = ['1'] - columns[1] = ['2'] - columns['b'] = ['3'] - - // Check that the order is still correct with iteration - let iteration = 0 - for (const [_, val] of columns) { - assertEquals(val, [iteration.toString()]) - iteration += 1 - } - assertEquals(columns.a, ['0']) - }) - await t.step('keys are accessible with Object.keys', () => { - const columns = new ColumnsMap() - columns['a'] = ['0'] - columns['b'] = ['1'] - columns[0] = ['2'] - assertEquals(Object.keys(columns), ['a', 'b', '0']) - assertEquals(Object.getOwnPropertyNames(columns), ['a', 'b', '0']) - }) - await t.step('size columns are permissible', () => { - const columns = new ColumnsMap() - // @ts-expect-error ts thinks size is protected property - columns['size'] = ['0'] - // @ts-expect-error ibid - assertEquals(columns.size, ['0']) - }) - await t.step('missing columns are undefined', () => { - const columns = new ColumnsMap() - columns['a'] = ['0'] - assertEquals(columns.b, undefined) - assertEquals(columns.size, undefined) - }) -}) diff --git a/bids-validator/src/types/columns.ts b/bids-validator/src/types/columns.ts deleted file mode 100644 index 77a1a06a6..000000000 --- a/bids-validator/src/types/columns.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Allow ColumnsMap to be accessed as an object too -export class ColumnsMap extends Map { - [key: string]: Map[keyof Map] | string[] - constructor() { - super() - const columns = new Map() as ColumnsMap - return new Proxy(columns, columnMapAccessorProxy) - } -} - -// Proxy handler to implement ColumnsMap type -const columnMapAccessorProxy = { - get: function ( - target: ColumnsMap, - prop: symbol | string, - receiver: ColumnsMap, - ) { - // Map.size exists, so we need to shadow it with the column contents - if (prop === 'size') return target.get('size') - const value = Reflect.get(target, prop, receiver) - if (typeof value === 'function') return value.bind(target) - if (prop === Symbol.iterator) return target[Symbol.iterator].bind(target) - if (value === undefined) return target.get(prop as string) - return value - }, - set: function (target: ColumnsMap, prop: string, value: string[]) { - target.set(prop, value) - return true - }, - has: function (target: ColumnsMap, prop: string) { - return Reflect.has(target, prop) - }, - ownKeys: function (target: ColumnsMap) { - return Array.from(target.keys()) - }, - getOwnPropertyDescriptor: function ( - target: ColumnsMap, - prop: string, - ): PropertyDescriptor { - return { enumerable: true, configurable: true, value: target.get(prop) } - }, -} diff --git a/bids-validator/src/types/filetree.ts b/bids-validator/src/types/filetree.ts deleted file mode 100644 index a250e2cc6..000000000 --- a/bids-validator/src/types/filetree.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FileIgnoreRules } from '../files/ignore.ts' - -export interface BIDSFile { - // Filename - name: string - // Dataset relative path for the file - path: string - // File size in bytes - size: number - // BIDS ignore status of the file - ignored: boolean - // ReadableStream to file raw contents - stream: ReadableStream - // Resolve stream to decoded utf-8 text - text: () => Promise - // Read a range of bytes - readBytes: (size: number, offset?: number) => Promise - // Access the parent directory - parent: FileTree - // File has been viewed - viewed: boolean -} - -/** - * Abstract FileTree for all environments (Deno, Browser, Python) - */ -export class FileTree { - // Relative path to this FileTree location - path: string - // Name of this directory level - name: string - files: BIDSFile[] - directories: FileTree[] - viewed: boolean - parent?: FileTree - #ignore: FileIgnoreRules - - constructor(path: string, name: string, parent?: FileTree, ignore?: FileIgnoreRules) { - this.path = path - this.files = [] - this.directories = [] - this.name = name - this.parent = parent - this.viewed = false - this.#ignore = ignore ?? new FileIgnoreRules([]) - } - - get ignored(): boolean { - return this.#ignore.test(this.path) - } - - _get(parts: string[]): BIDSFile | FileTree | undefined { - if (parts.length === 0) { - return undefined - } else if (parts.length === 1) { - return this.files.find((x) => x.name === parts[0]) || - this.directories.find((x) => x.name === parts[0]) - } else { - const nextDir = this.directories.find((x) => x.name === parts[0]) - return nextDir?._get(parts.slice(1, parts.length)) - } - } - - get(path: string): BIDSFile | FileTree | undefined { - if (path.startsWith('/')) { - path = path.slice(1) - } - return this._get(path.split('/')) - } - - contains(parts: string[]): boolean { - const value = this._get(parts) - return value ? (value.viewed = true) : false - } -} diff --git a/bids-validator/src/types/issues.ts b/bids-validator/src/types/issues.ts deleted file mode 100644 index 9597bfe69..000000000 --- a/bids-validator/src/types/issues.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { BIDSFile } from './filetree.ts' - -export type Severity = 'warning' | 'error' | 'ignore' - -/** - * Updated internal Issue structure for schema based validation - */ -export interface Issue { - code: string - subCode?: string - severity?: Severity - location?: string - issueMessage?: string - suggestion?: string - affects?: string[] - rule?: string - line?: number - character?: number -} - -/** - * For defining internal issues quickly - */ -export interface IssueDefinition { - severity: Severity - reason: string -} -export type IssueDefinitionRecord = Record - -/** - * File allowing extra context for the issue found - */ -export type IssueFile = Omit & { - evidence?: string - line?: number - character?: number -} diff --git a/bids-validator/src/types/schema.ts b/bids-validator/src/types/schema.ts deleted file mode 100644 index 066ff9117..000000000 --- a/bids-validator/src/types/schema.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Schema structure returned by loadSchema - */ - -export interface Format { - pattern: string -} - -export interface Entity { - name: string - type: string - format: string -} - -export interface Value { - value: string -} - -export interface SchemaObjects { - datatypes: Record - enums: Record - entities: Record - extensions: Record - files: Record - formats: Record - suffixes: Record -} - -export interface SchemaRules { - entities: string[] - files: SchemaFiles - modalities: Record -} - -export interface SchemaFiles { - common: Record - deriv: Record - raw: Record -} - -export interface ExpressionTest { - expression: string - result: string -} - -export interface SchemaMeta { - expression_tests: ExpressionTest[] -} - -export interface Schema { - objects: SchemaObjects - rules: SchemaRules - schema_version: string - meta: SchemaMeta -} - -export interface SchemaIssue { - code: string - message: string - level?: string -} - -export type GenericSchema = { [key: string]: GenericRule | GenericSchema } - -export interface GenericRule { - selectors?: string[] - checks?: string[] - columns?: Record - additional_columns?: string - initial_columns?: string[] - fields: Record - issue?: SchemaIssue - extensions?: string[] - suffixes?: string[] - stem?: string - path?: string - datatypes?: string[] - pattern?: string - name?: string - format?: string - required?: string - index_columns?: string[] - metadata?: Record -} - -export interface SchemaFields { - level: string - level_addendum?: string - issue?: SchemaIssue -} - -export interface SchemaType { - type: string - format?: string - enum?: string[] - items?: SchemaType[] - minItems?: number - maxItems?: number - unit?: string -} - -interface AnyOf { - anyOf: SchemaType[] -} - -export type SchemaTypeLike = AnyOf | SchemaType diff --git a/bids-validator/src/types/validation-result.ts b/bids-validator/src/types/validation-result.ts deleted file mode 100644 index 4087f779a..000000000 --- a/bids-validator/src/types/validation-result.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { DatasetIssues } from '../issues/datasetIssues.ts' - -export interface SubjectMetadata { - participantId: string - age: number - sex: string -} -/* - BodyPart: {}, - ScannerManufacturer: {}, - ScannerManufacturersModelName: {}, - TracerName: {}, - TracerRadionuclide: {}, -*/ - -export interface SummaryOutput { - sessions: string[] - subjects: string[] - subjectMetadata: SubjectMetadata[] - tasks: string[] - modalities: string[] - secondaryModalities: string[] - totalFiles: number - size: number - dataProcessed: boolean - pet: Record - dataTypes: string[] - schemaVersion: string -} - -/** - * The output of a validation run - */ -export interface ValidationResult { - issues: DatasetIssues - summary: SummaryOutput - derivativesSummary?: Record -} diff --git a/bids-validator/src/utils/errors.ts b/bids-validator/src/utils/errors.ts deleted file mode 100644 index e5a99e934..000000000 --- a/bids-validator/src/utils/errors.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class SchemaStructureError extends Error { - schemaPath: string - constructor(schemaPath: string) { - super(`Validator attempted to access ${schemaPath}, but it wasn't there.`) - this.name = 'SchemaStructureError' - this.schemaPath = schemaPath - } -} diff --git a/bids-validator/src/utils/logger.test.ts b/bids-validator/src/utils/logger.test.ts deleted file mode 100644 index ee3c3d7dc..000000000 --- a/bids-validator/src/utils/logger.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { assertEquals } from '@std/assert' -import { parseStack } from './logger.ts' - -Deno.test('logger', async (t) => { - await t.step('test stack trace behavior for regular invocation', () => { - const stack = `Error - at Object.get (file:///bids-validator/src/utils/logger.ts:39:19) - at file:///bids-validator/src/schema/context.ts:170:16 - at async BIDSContext.loadColumns (file:///bids-validator/src/schema/context.ts:163:20) - at async Function.allSettled () - at async BIDSContext.asyncLoads (file:///bids-validator/src/schema/context.ts:182:5) - at async validate (file:///bids-validator/src/validators/bids.ts:78:5) - at async main (file:///bids-validator/src/main.ts:26:24) - at async file:///bids-validator/bids-validator-deno:4:1 -` - assertEquals( - parseStack(stack), - `file:///bids-validator/src/schema/context.ts:170:16`, - ) - }) - await t.step('test stack trace behavior for catch invocation', () => { - const stack = `Error - at Object.get (file:///bids-validator/bids-validator/src/utils/logger.ts:31:19) - at loadHeader (file:///bids-validator/bids-validator/src/files/nifti.ts:18:12) - at async BIDSContext.loadNiftiHeader (file:///bids-validator/bids-validator/src/schema/context.ts:155:27) -` - assertEquals( - parseStack(stack), - 'loadHeader (file:///bids-validator/bids-validator/src/files/nifti.ts:18:12)', - ) - }) -}) diff --git a/bids-validator/src/utils/logger.ts b/bids-validator/src/utils/logger.ts deleted file mode 100644 index e15f254e5..000000000 --- a/bids-validator/src/utils/logger.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ConsoleHandler, getLogger, type LevelName, type Logger, setup } from '@std/log' - -/** - * Setup a console logger used with the --debug flag - */ -export function setupLogging(level: LevelName) { - setup({ - handlers: { - console: new ConsoleHandler(level), - }, - - loggers: { - '@bids/validator': { - level, - handlers: ['console'], - }, - }, - }) -} - -export function parseStack(stack: string) { - const lines = stack.split('\n') - const caller = lines[2].trim() - const token = caller.split('at ') - return token[1] -} - -const loggerProxyHandler = { - // deno-lint-ignore no-explicit-any - get: function (_: any, prop: keyof Logger) { - const logger = getLogger('@bids/validator') - const stack = new Error().stack - if (stack) { - const callerLocation = parseStack(stack) - logger.debug(`Logger invoked at "${callerLocation}"`) - } - const logFunc = logger[prop] as typeof logger.warn - return logFunc.bind(logger) - }, -} - -const logger = new Proxy(getLogger('@bids/validator'), loggerProxyHandler) - -export { logger } diff --git a/bids-validator/src/utils/memoize.ts b/bids-validator/src/utils/memoize.ts deleted file mode 100644 index 3f4aae25c..000000000 --- a/bids-validator/src/utils/memoize.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type WithCache = T & { cache: Map } -interface HasParent { - parent: { path: string } -} - -export const memoize = ( - fn: (...args: any[]) => T, -): WithCache<(...args: any[]) => T> => { - const cache = new Map() - const cached = function (this: any, val: T) { - return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val) - } - cached.cache = cache - return cached -} - -export function filememoizeAsync( - fn: (file: F) => Promise, -): WithCache<(file: F) => Promise> { - const cache = new Map>() - const cached = async function (this: any, file: F): Promise { - let subcache = cache.get(file.parent.path) - if (!subcache) { - subcache = new Map() - cache.set(file.parent.path, subcache) - } - let val = subcache.get(file) - if (!val) { - val = await fn.call(this, file) - subcache.set(file, val) - } - return val - } - cached.cache = cache - return cached -} diff --git a/bids-validator/src/utils/objectPathHandler.ts b/bids-validator/src/utils/objectPathHandler.ts deleted file mode 100644 index 8abaa525e..000000000 --- a/bids-validator/src/utils/objectPathHandler.ts +++ /dev/null @@ -1,24 +0,0 @@ -// https://stackoverflow.com/questions/67849097/typescript-type-narrowing-not-working-when-looping -export const hasProp = ( - obj: T, - prop: K, -): obj is T & Record => { - return Object.prototype.hasOwnProperty.call(obj, prop) -} - -export const objectPathHandler = { - get(target: unknown, property: string) { - let res = target - if (typeof property === 'symbol') { - return res - } - for (const prop of property.split('.')) { - if (hasProp(res, prop)) { - res = res[prop] - } else { - return undefined - } - } - return res - }, -} diff --git a/bids-validator/src/utils/output.ts b/bids-validator/src/utils/output.ts deleted file mode 100644 index ec8c6ae8c..000000000 --- a/bids-validator/src/utils/output.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Utilities for formatting human readable output (CLI or other UIs) - */ -import { Table } from '@cliffy/table' -import * as colors from '@std/fmt/colors' -import { format as prettyBytes } from '@std/fmt/bytes' -import type { SummaryOutput, ValidationResult } from '../types/validation-result.ts' -import type { Issue, Severity } from '../types/issues.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' - -interface LoggingOptions { - verbose: boolean -} - -/** - * Format for Unix consoles - * - * Returns the full output string with newlines - */ -export function consoleFormat( - result: ValidationResult, - options?: LoggingOptions, -): string { - const output = [] - if (result.issues.size === 0) { - output.push(colors.green('This dataset appears to be BIDS compatible.')) - } else { - ;(['warning', 'error'] as Severity[]).map((severity) => { - output.push(...formatIssues(result.issues.filter({ severity }), options, severity)) - }) - } - output.push('') - output.push(formatSummary(result.summary)) - output.push('') - return output.join('\n') -} - -function formatIssues( - dsIssues: DatasetIssues, - options?: LoggingOptions, - severity = 'error', -): string[] { - const output = [] - const color = severity === 'error' ? 'red' : 'yellow' - - for (const [code, issues] of dsIssues.groupBy('code').entries()) { - if (issues.size === 0 || typeof code !== 'string') { - continue - } - const codeMessage = issues.codeMessages.get(code) ?? '' - output.push( - '\t' + - colors[color]( - `[${severity.toUpperCase()}] ${code} ${codeMessage}`, - ), - ) - - const subCodes = issues.groupBy('subCode') - if (subCodes.size === 1 && subCodes.has('None')) { - output.push(...formatFiles(issues, options)) - } else { - for (const [subCode, subIssues] of subCodes) { - if (subIssues.size === 0) { - continue - } - output.push('\t\t' + colors[color](`${subCode}`)) - output.push(...formatFiles(subIssues, options)) - } - } - - output.push( - colors.cyan( - `\tPlease visit ${helpUrl(code)} for existing conversations about this issue.`, - ), - ) - output.push('') - } - return output -} - -function formatFiles(issues: DatasetIssues, options?: LoggingOptions): string[] { - const output = [] - let issueDetails: Array = ['location', 'issueMessage'] - if (options?.verbose) { - issueDetails = ['location', 'issueMessage', 'rule'] - } - - const fileCount = options?.verbose ? undefined : 2 - - const toPrint = issues.issues.slice(0, fileCount) - toPrint.map((issue: Issue) => { - const fileOut: string[] = [] - issueDetails.map((key) => { - if (Object.hasOwn(issue, key) && issue[key]) { - fileOut.push(`${issue[key]}`) - } - }) - output.push('\t\t' + fileOut.join(' - ')) - }) - if (fileCount && fileCount < issues.size) { - output.push('') - output.push(`\t\t${issues.size - fileCount} more files with the same issue`) - } - output.push('') - return output -} - -/** - * Format for the summary - */ -function formatSummary(summary: SummaryOutput): string { - const output = [] - const numSessions = summary.sessions.length > 0 ? summary.sessions.length : 1 - - // data - const column1 = [ - summary.totalFiles + ' ' + 'Files' + ', ' + prettyBytes(summary.size), - summary.subjects.length + - ' - ' + - 'Subjects ' + - numSessions + - ' - ' + - 'Sessions', - ], - column2 = summary.tasks, - column3 = summary.modalities - - const longestColumn = Math.max(column1.length, column2.length, column3.length) - const pad = ' ' - - // headers - const headers = [ - pad, - colors.magenta('Summary:') + pad, - colors.magenta('Available Tasks:') + pad, - colors.magenta('Available Modalities:'), - ] - - // rows - const rows = [] - for (let i = 0; i < longestColumn; i++) { - const val1 = column1[i] ? column1[i] + pad : '' - const val2 = column2[i] ? column2[i] + pad : '' - const val3 = column3[i] ? column3[i] : '' - rows.push([pad, val1, val2, val3]) - } - const table = new Table() - .header(headers) - .body(rows) - .border(false) - .padding(1) - .indent(2) - .toString() - - output.push(table) - - output.push('') - - //Neurostars message - output.push( - colors.cyan( - '\tIf you have any questions, please post on https://neurostars.org/tags/bids.', - ), - ) - - return output.join('\n') -} - -function helpUrl(code: string): string { - // Provide a link to NeuroStars - return `https://neurostars.org/search?q=${code}` -} - -export function resultToJSONStr(result: ValidationResult): string { - return JSON.stringify(result, (key, value) => { - if (value?.parent) { - // Remove parent reference to avoid circular references - value.parent = undefined - } - if (value instanceof Map) { - return Object.fromEntries(value) - } else { - return value - } - }) -} diff --git a/bids-validator/src/validators/bids.test.ts b/bids-validator/src/validators/bids.test.ts deleted file mode 100644 index 32ed2658a..000000000 --- a/bids-validator/src/validators/bids.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { assert } from '@std/assert' -import { pathsToTree } from '../files/filetree.ts' -import { validate } from './bids.ts' - -const dataset = pathsToTree([ - '/dataset_description.json', - '/sub-01/anat/sub-01_T1w.nii.gz', -]) - -Deno.test('Smoke tests of main validation function', async (t) => { - await t.step('Validating trivial with blacklist', async () => { - let result = await validate(dataset, { - datasetPath: '/dataset', - debug: 'INFO', - ignoreNiftiHeaders: true, - blacklistModalities: [], - }) - assert(result.issues.get({ code: 'BLACKLISTED_MODALITY' }).length === 0) - - result = await validate(dataset, { - datasetPath: '/dataset', - debug: 'INFO', - ignoreNiftiHeaders: true, - blacklistModalities: ['MRI'], - }) - assert(result.issues.get({ code: 'BLACKLISTED_MODALITY' }).length === 1) - - result = await validate(dataset, { - datasetPath: '/dataset', - debug: 'INFO', - ignoreNiftiHeaders: true, - blacklistModalities: ['MEG'], - }) - assert(result.issues.get({ code: 'BLACKLISTED_MODALITY' }).length === 0) - - result = await validate(dataset, { - datasetPath: '/dataset', - debug: 'INFO', - ignoreNiftiHeaders: true, - blacklistModalities: ['MEG', 'MRI'], - }) - assert(result.issues.get({ code: 'BLACKLISTED_MODALITY' }).length === 1) - }) - await t.step('Validate configuration', async () => { - let result = await validate( - dataset, - { - datasetPath: '/dataset', - debug: 'INFO', - blacklistModalities: [], - }, - { - ignore: [{ location: '/dataset_description.json' }], - }, - ) - let errors = result.issues.filter({ severity: 'error' }) - let warnings = result.issues.filter({ severity: 'warning' }) - let ignored = result.issues.filter({ severity: 'ignore' }) - assert(errors.get({ code: 'JSON_KEY_RECOMMENDED' }).length === 0) - assert(ignored.get({ code: 'JSON_KEY_RECOMMENDED' }).length > 0) - assert(errors.get({ location: '/dataset_description.json' }).length === 0) - assert(warnings.get({ location: '/dataset_description.json' }).length === 0) - }) -}) diff --git a/bids-validator/src/validators/bids.ts b/bids-validator/src/validators/bids.ts deleted file mode 100644 index c51b94855..000000000 --- a/bids-validator/src/validators/bids.ts +++ /dev/null @@ -1,180 +0,0 @@ -import type { ContextCheckFunction, DSCheckFunction } from '../types/check.ts' -import type { BIDSFile, FileTree } from '../types/filetree.ts' -import { loadJSON } from '../files/json.ts' -import type { IssueFile, Severity } from '../types/issues.ts' -import type { GenericSchema } from '../types/schema.ts' -import type { ValidationResult } from '../types/validation-result.ts' -import { applyRules } from '../schema/applyRules.ts' -import { walkFileTree } from '../schema/walk.ts' -import { loadSchema } from '../setup/loadSchema.ts' -import type { Config, ValidatorOptions } from '../setup/options.ts' -import { Summary } from '../summary/summary.ts' -import { filenameIdentify } from './filenameIdentify.ts' -import { filenameValidate } from './filenameValidate.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import { emptyFile } from './internal/emptyFile.ts' -import { sidecarWithoutDatafile, unusedStimulus } from './internal/unusedFile.ts' -import { type BIDSContext, BIDSContextDataset } from '../schema/context.ts' -import type { parseOptions } from '../setup/options.ts' -import { hedValidate } from './hed.ts' -import { citationValidate } from './citation.ts' -import { logger } from '../utils/logger.ts' - -/** - * Ordering of checks to apply - */ -const perContextChecks: ContextCheckFunction[] = [ - emptyFile, - filenameIdentify, - filenameValidate, - applyRules, - hedValidate, -] - -const perDSChecks: DSCheckFunction[] = [ - unusedStimulus, - sidecarWithoutDatafile, - citationValidate, -] - -/** - * Full BIDS schema validation entrypoint - */ -export async function validate( - fileTree: FileTree, - options: ValidatorOptions, - config?: Config, -): Promise { - const summary = new Summary() - const schema = await loadSchema(options.schema) - summary.schemaVersion = schema.schema_version - - /* There should be a dataset_description in root, this will tell us if we - * are dealing with a derivative dataset - */ - const ddFile = fileTree.get('dataset_description.json') as BIDSFile - - const dsContext = new BIDSContextDataset({ options, schema, tree: fileTree }) - if (ddFile) { - dsContext.dataset_description = await loadJSON(ddFile).catch((error) => { - if (error.key) { - dsContext.issues.add({ code: error.key, location: ddFile.path }) - return {} as Record - } else { - throw error - } - }) - summary.dataProcessed = dsContext.dataset_description.DatasetType === 'derivative' - } else { - dsContext.issues.add({ - code: 'MISSING_DATASET_DESCRIPTION', - affects: ['/dataset_description.json'], - }) - } - - const bidsDerivatives: FileTree[] = [] - const nonstdDerivatives: FileTree[] = [] - fileTree.directories = fileTree.directories.filter((dir) => { - if (['sourcedata', 'code'].includes(dir.name)) { - return false - } - if (dir.name !== 'derivatives') { - return true - } - for (const deriv of dir.directories) { - if (deriv.get('dataset_description.json')) { - // New root for the derivative dataset - deriv.parent = undefined - bidsDerivatives.push(deriv) - } else { - nonstdDerivatives.push(deriv) - } - } - // Remove derivatives from the main fileTree - return false - }) - - for await (const context of walkFileTree(dsContext)) { - // TODO - Skip ignored files for now (some tests may reference ignored files) - if (context.file.ignored) { - continue - } - if ( - dsContext.dataset_description.DatasetType == 'raw' && - context.file.path.includes('derivatives') - ) { - continue - } - await context.asyncLoads() - // Run majority of checks - for (const check of perContextChecks) { - await check(schema as unknown as GenericSchema, context) - } - await summary.update(context) - } - for (const check of perDSChecks) { - await check(schema as unknown as GenericSchema, dsContext) - } - - const modalitiesRule = schema.rules.modalities as Record - const blacklistedDatatypes = new Map() - if (options.blacklistModalities) { - // Map blacklisted datatypes back to the modality that generated them - for (const modality of options.blacklistModalities) { - const datatypes = modalitiesRule[modality.toLowerCase()]?.datatypes as string[] - if (datatypes) { - for (const datatype of datatypes) { - blacklistedDatatypes.set(datatype, modality) - } - } else { - logger.warn(`Attempted to blacklist unknown modality: ${modality}`) - } - } - } - - const blacklistedDirs = fileTree.directories.filter((dir) => dir.name.startsWith('sub-')) - .flatMap((dir) => dir.directories) - .flatMap((dir) => dir.name.startsWith('ses-') ? dir.directories : [dir]) - .filter((dir) => blacklistedDatatypes.has(dir.name)) - - blacklistedDirs.forEach((dir) => { - dsContext.issues.add({ - code: 'BLACKLISTED_MODALITY', - location: dir.path, - }) - }) - - const derivativesSummary: Record = {} - if (options.recursive) { - await Promise.allSettled( - bidsDerivatives.map(async (deriv) => { - derivativesSummary[deriv.name] = await validate(deriv, options) - return derivativesSummary[deriv.name] - }), - ) - } - - if (config) { - for (const level of ['ignore', 'warning', 'error'] as const) { - for (const filter of config[level] ?? []) { - for (const issue of dsContext.issues.filter(filter).issues) { - issue.severity = level as Severity - } - } - } - } - - if (options.ignoreWarnings) { - dsContext.issues = dsContext.issues.filter({ severity: 'error' }) - } - - const output: ValidationResult = { - issues: dsContext.issues, - summary: summary.formatOutput(), - } - - if (Object.keys(derivativesSummary).length) { - output['derivativesSummary'] = derivativesSummary - } - return output -} diff --git a/bids-validator/src/validators/citation.test.ts b/bids-validator/src/validators/citation.test.ts deleted file mode 100644 index 16bcfbcba..000000000 --- a/bids-validator/src/validators/citation.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { assert } from '@std/assert' -import { pathsToTree } from '../files/filetree.ts' -import { BIDSFileDeno } from '../files/deno.ts' -import { citationValidate } from './citation.ts' -import { BIDSContextDataset } from '../schema/context.ts' -import { GenericSchema } from '../types/schema.ts' -import { loadSchema } from '../setup/loadSchema.ts' - -Deno.test('citation validation', async (t) => { - const schema = await loadSchema() - await t.step('no errors on the good citation.cff', async () => { - const tree = pathsToTree(['CITATION.cff']) - const dsContext = new BIDSContextDataset({ tree }) - const file = new BIDSFileDeno('tests/data/citation', 'good.cff') - tree.files[0].text = () => file.text() - await citationValidate({} as GenericSchema, dsContext) - assert(dsContext.issues.size === 0) - }) - await t.step('An error on the bad citation.cff', async () => { - const tree = pathsToTree(['CITATION.cff']) - const dsContext = new BIDSContextDataset({ tree }) - const file = new BIDSFileDeno('tests/data/citation', 'bad.cff') - tree.files[0].text = () => file.text() - await citationValidate({} as GenericSchema, dsContext) - assert(dsContext.issues.get({ code: 'CITATION_CFF_VALIDATION_ERROR' }).length === 1) - }) -}) diff --git a/bids-validator/src/validators/citation.ts b/bids-validator/src/validators/citation.ts deleted file mode 100644 index 905ef80ef..000000000 --- a/bids-validator/src/validators/citation.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { GenericSchema } from '../types/schema.ts' -import type { BIDSFile, FileTree } from '../types/filetree.ts' -import type { BIDSContextDataset } from '../schema/context.ts' -import { schema as citationSchema } from '@bids/schema/citation' -import { compile } from './json.ts' -import type { DefinedError } from '@ajv' -import { parse } from '@std/yaml' - -const citationFilename = 'CITATION.cff' - -export async function citationValidate( - schema: GenericSchema, - dsContext: BIDSContextDataset, -) { - const citationFile = dsContext.tree.get(citationFilename) - if (!citationFile || 'directories' in citationFile) return - let citation: unknown = {} - try { - citation = parse(await citationFile.text()) - } catch (error) { - dsContext.issues.add({ - code: 'FILE_READ', - issueMessage: `Error from attempted read of file:\n${error}`, - location: citationFilename, - }) - return - } - const validate = compile(citationSchema) - if (!validate(citation)) { - for (const err of validate.errors as DefinedError[]) { - dsContext.issues.add({ - code: 'CITATION_CFF_VALIDATION_ERROR', - subCode: err['instancePath'], - issueMessage: err['message'], - location: citationFilename, - }) - } - } -} diff --git a/bids-validator/src/validators/filenameIdentify.test.ts b/bids-validator/src/validators/filenameIdentify.test.ts deleted file mode 100644 index 78ca37ebc..000000000 --- a/bids-validator/src/validators/filenameIdentify.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { assertEquals } from '@std/assert' -import { BIDSContext } from '../schema/context.ts' -import { _findRuleMatches, datatypeFromDirectory, hasMatch } from './filenameIdentify.ts' -import { BIDSFileDeno } from '../files/deno.ts' -import { FileIgnoreRules } from '../files/ignore.ts' -import { loadSchema } from '../setup/loadSchema.ts' - -const PATH = 'tests/data/valid_dataset' -const schema = await loadSchema() -const ignore = new FileIgnoreRules([]) - -const node = { - stem: 'participants', -} - -const recurseNode = { - recurse: { - suffixes: 'bold', - }, -} - -const schemaPath = 'test.schema.path' - -Deno.test('test _findRuleMatches', async (t) => { - // base case - await t.step('Rule stem matches', async () => { - const fileName = 'participants.json' - const file = new BIDSFileDeno(PATH, fileName, ignore) - const context = new BIDSContext(file) - _findRuleMatches(node, schemaPath, context) - assertEquals(context.filenameRules[0], schemaPath) - }) - - //recurse case - await t.step( - 'Non-terminal schema node, should recurse then match', - async () => { - const fileName = 'task-rest_bold.json' - const file = new BIDSFileDeno(PATH, fileName, ignore) - const context = new BIDSContext(file) - _findRuleMatches(recurseNode, schemaPath, context) - assertEquals(context.filenameRules[0], `${schemaPath}.recurse`) - }, - ) -}) - -Deno.test('test datatypeFromDirectory', (t) => { - const filesToTest = [ - ['/sub-01/ses-01/func/sub-01_ses-01_task-nback_run-01_bold.nii', 'func'], - ['/sub-01/ses-01/anat/sub-01_ses-01_T1w.nii', 'anat'], - ] - filesToTest.map((test) => { - const file = new BIDSFileDeno(PATH, test[0], ignore) - const context = new BIDSContext(file) - datatypeFromDirectory(schema, context) - assertEquals(context.datatype, test[1]) - }) -}) - -Deno.test('test hasMatch', async (t) => { - await t.step('hasMatch', async () => { - const fileName = '/sub-01/ses-01/func/sub-01_ses-01_task-nback_run-01_bold.nii' - const file = new BIDSFileDeno(PATH, fileName, ignore) - const context = new BIDSContext(file) - hasMatch(schema, context) - }) - - await t.step('No match', async () => { - const tmpFile = Deno.makeTempFileSync() - const parts = tmpFile.split('/') - const file = new BIDSFileDeno( - parts.slice(0, parts.length - 1).join('/'), - parts[parts.length - 1], - ignore, - ) - - const context = new BIDSContext(file) - await hasMatch(schema, context) - assertEquals( - context.dataset.issues.get({ - location: context.file.path, - code: 'NOT_INCLUDED', - }).length, - 1, - ) - Deno.removeSync(tmpFile) - }) - await t.step('2 matches, no pruning', async () => { - const path = `${PATH}/../bids-examples/fnirs_automaticity` - const fileName = 'events.json' - const file = new BIDSFileDeno(path, fileName, ignore) - const context = new BIDSContext(file) - context.filenameRules = [ - 'rules.files.raw.task.events__mri', - 'rules.files.raw.task.events__pet', - ] - await hasMatch(schema, context) - assertEquals(context.filenameRules.length, 2) - }) -}) diff --git a/bids-validator/src/validators/filenameIdentify.ts b/bids-validator/src/validators/filenameIdentify.ts deleted file mode 100644 index f4becaad9..000000000 --- a/bids-validator/src/validators/filenameIdentify.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* - * filenameIdentify.ts attempts to determine which schema rules from - * `schema.rules.files` might apply to a given file context by looking at the - * files suffix then its location in the directory hierarchy, and finally at - * its extensions and entities. Ideally we end up with a single rule to - * validate against. We try to take as broad an approach to finding a single - * file rule as possible to generate the most possible errors for incorrectly - * named files. Historically a regex was applied that was pass/fail with - * little in the way of feed back. This way we can say hey you got the suffix - * correct, but the directory is slightly off, or some entities are missing, - * or too many are there for this rule. All while being able to point at an - * object in the schema for reference. - */ -// @ts-nocheck -import { globToRegExp, SEPARATOR_PATTERN } from '@std/path' -import type { GenericSchema, Schema } from '../types/schema.ts' -import type { BIDSContext } from '../schema/context.ts' -import type { lookupModality } from '../schema/modalities.ts' -import type { CheckFunction } from '../types/check.ts' -import { lookupEntityLiteral } from './filenameValidate.ts' - -const CHECKS: CheckFunction[] = [ - datatypeFromDirectory, - findRuleMatches, - hasMatch, - cleanContext, -] - -export async function filenameIdentify(schema, context) { - for (const check of CHECKS) { - await check(schema as unknown as GenericSchema, context) - } -} - -function findRuleMatches(schema, context) { - const schemaPath = 'rules.files' - Object.keys(schema[schemaPath]).map((key) => { - if ( - key == 'deriv' && - context.dataset.dataset_description.DatasetType != 'derivative' - ) { - return - } - const path = `${schemaPath}.${key}` - _findRuleMatches(schema[path], path, context) - }) - return Promise.resolve() -} - -/* Schema rules specifying valid filenames follow a variety of patterns. - * 'path', 'stem' or 'suffixies' contain the most unique identifying - * information for a rule. We don't know what kind of filename the context is, - * so if one of these three match the respective value in the context lets - * assume that this schema rule is applicable to this file. - */ -export function _findRuleMatches(node, path, context) { - if ( - (`/${node.path}` === context.path) || - (node.stem && matchStemRule(node, context)) || - ('suffixes' in node && node.suffixes.includes(context.suffix)) - ) { - context.filenameRules.push(path) - return - } - if ( - !('path' in node || 'stem' in node || 'suffixes' in node) && - typeof node === 'object' - ) { - Object.keys(node).map((key) => { - _findRuleMatches(node[key], `${path}.${key}`, context) - }) - } -} - -function matchStemRule(node, context): boolean { - if (!context.file.name.split('.')[0].match(globToRegExp(node.stem))) { - return false - } - if (node.datatypes) { - return node.datatypes.includes(context.datatype) - } - return true -} - -export async function datatypeFromDirectory(schema, context) { - const subEntity = schema.objects.entities.subject.name - const sesEntity = schema.objects.entities.session.name - const parts = context.file.path.split(SEPARATOR_PATTERN) - const datatypeIndex = parts.length - 2 - if (datatypeIndex < 1) { - return Promise.resolve() - } - const dirDatatype = parts[datatypeIndex] - if (dirDatatype === 'phenotype') { - // Phenotype is a pseudo-datatype for now. - context.datatype = dirDatatype - return Promise.resolve() - } - for (const key in schema.rules.modalities) { - if (schema.rules.modalities[key].datatypes.includes(dirDatatype)) { - context.modality = key - context.datatype = dirDatatype - return Promise.resolve() - } - } -} - -export function hasMatch(schema, context) { - if ( - context.filenameRules.length === 0 && - context.file.path !== '/.bidsignore' - ) { - context.dataset.issues.add({ - code: 'NOT_INCLUDED', - location: context.path, - }) - } - - /* we have matched multiple rules and a datatype, lets see if we have one - * rule with the same datatype, if so just use that one. - */ - if (context.filenameRules.length > 1) { - const datatypeMatch = context.filenameRules.filter((rulePath) => { - if (Array.isArray(schema[rulePath].datatypes)) { - return schema[rulePath].datatypes.includes(context.datatype) - } else { - return false - } - }) - if (datatypeMatch.length > 0) { - context.filenameRules = datatypeMatch - } - } - - /* Filtering applicable rules based on datatypes failed, lets see if the - * entities and extensions are enough to find a single rule to use. - */ - if (context.filenameRules.length > 1) { - const entExtMatch = context.filenameRules.filter((rulePath) => { - return entitiesExtensionsInRule(schema, context, rulePath) - }) - if (entExtMatch.length > 0) { - context.filenameRules = entExtMatch - } - } - - return Promise.resolve() -} - -/* Test if all of a given context's extension and entities are present in a - * given rule. Only used to see if one rule is more applicable than another - * after suffix and datatype matches couldn't find only one rule. - */ -function entitiesExtensionsInRule( - schema: GenericSchema, - context: BIDSContext, - path: string, -): boolean { - const rule = schema[path] - const fileEntities = Object.keys(context.entities) - const ruleEntities = rule.entities - ? Object.keys(rule.entities).map((key) => lookupEntityLiteral(key, schema)) - : [] - const extInRule = !rule.extensions || - (rule.extensions && rule.extensions.includes(context.extension)) - const entInRule = !rule.entities || - (rule.entities && - fileEntities.every((ent) => { - return ruleEntities.includes(ent) - })) - return extInRule && entInRule -} - -/* If none of the rules applicable to a filename use entities or what not, - * lets remove them from the context so we don't trigger any unintended rules - */ -function cleanContext(schema, context) { - const rules = context.filenameRules.map((path) => schema[path]) - const filenameParts = [ - ['entities', 'entities', {}], - ['extensions', 'extension', ''], - ['suffixes', 'suffix', ''], - ] - filenameParts.map((part) => { - if ( - rules.every( - (rule) => !rule[part[0]] || Object.keys(rule[part[0]]).length === 0, - ) - ) { - context[part[1]] = part[2] - } - }) -} diff --git a/bids-validator/src/validators/filenameValidate.test.ts b/bids-validator/src/validators/filenameValidate.test.ts deleted file mode 100644 index 9771d32c0..000000000 --- a/bids-validator/src/validators/filenameValidate.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { FileTree } from '../types/filetree.ts' -import type { GenericSchema } from '../types/schema.ts' -import { assertEquals } from '@std/assert' -import { BIDSContext } from '../schema/context.ts' -import { type atRoot, type entityLabelCheck, missingLabel } from './filenameValidate.ts' -import type { BIDSFileDeno } from '../files/deno.ts' -import { pathToFile } from '../files/filetree.ts' -import type { FileIgnoreRules } from '../files/ignore.ts' -import { loadSchema } from '../setup/loadSchema.ts' - -const schema = (await loadSchema()) as unknown as GenericSchema - -Deno.test('test missingLabel', async (t) => { - await t.step('File with underscore and no hyphens errors out.', async () => { - const context = new BIDSContext(pathToFile('/no_label_entities.wav')) - // Need some suffix rule to trigger the check, - // otherwise this is trigger-happy. - context.filenameRules = ['rules.files.raw.dwi.dwi'] - - await missingLabel(schema, context) - assertEquals( - context.dataset.issues - .get({ - location: context.file.path, - code: 'ENTITY_WITH_NO_LABEL', - }).length, - 1, - ) - }) - - await t.step( - "File with underscores and hyphens doesn't error out.", - async () => { - const context = new BIDSContext(pathToFile('/we-do_have-some_entities.wav')) - // Doesn't really matter that the rule doesn't apply - context.filenameRules = ['rules.files.raw.dwi.dwi'] - - await missingLabel(schema, context) - assertEquals( - context.dataset.issues.get({ - location: context.file.path, - code: 'ENTITY_WITH_NO_LABEL', - }).length, - 0, - ) - }, - ) -}) diff --git a/bids-validator/src/validators/filenameValidate.ts b/bids-validator/src/validators/filenameValidate.ts deleted file mode 100644 index 103384786..000000000 --- a/bids-validator/src/validators/filenameValidate.ts +++ /dev/null @@ -1,342 +0,0 @@ -import type { ContextCheckFunction, RuleCheckFunction } from '../types/check.ts' -import { DatasetIssues } from '../issues/datasetIssues.ts' -import type { BIDSContext } from '../schema/context.ts' -import type { Entity, Format, GenericSchema, Schema } from '../types/schema.ts' -import { SEPARATOR_PATTERN } from '@std/path' -import { hasProp } from '../utils/objectPathHandler.ts' - -const sidecarExtensions = ['.json', '.tsv', '.bvec', '.bval'] - -const CHECKS: ContextCheckFunction[] = [ - missingLabel, - atRoot, - entityLabelCheck, - checkRules, - reconstructionFailure, -] - -export async function filenameValidate( - schema: GenericSchema, - context: BIDSContext, -) { - for (const check of CHECKS) { - await check(schema, context) - } - return Promise.resolve() -} - -export function isAtRoot(context: BIDSContext) { - if (context.file.path.split(SEPARATOR_PATTERN).length !== 2) { - return false - } - return true -} - -export async function missingLabel( - schema: GenericSchema, - context: BIDSContext, -) { - if (!context.filenameRules.some((rule) => 'suffixes' in schema[rule])) { - return Promise.resolve() - } - const fileNoLabelEntities = Object.keys(context.entities).filter( - (key) => context.entities[key] === 'NOENTITY', - ) - - const fileEntities = Object.keys(context.entities).filter( - (key) => !fileNoLabelEntities.includes(key), - ) - - if (fileNoLabelEntities.length) { - context.dataset.issues.add({ - code: 'ENTITY_WITH_NO_LABEL', - location: context.path, - issueMessage: fileNoLabelEntities.join(', '), - }) - } - return Promise.resolve() -} - -export function atRoot(schema: GenericSchema, context: BIDSContext) { - /* - if (fileIsAtRoot && !sidecarExtensions.includes(context.extension)) { - // create issue for data file in root of dataset - } - */ - return Promise.resolve() -} - -export function lookupEntityLiteral(name: string, schema: GenericSchema) { - if ( - schema.objects && - hasProp(schema.objects, 'entities') && - hasProp(schema.objects.entities, name) - ) { - const entityObj = schema.objects.entities[name] - if (hasProp(entityObj, 'name')) { - return entityObj.name - } - } - // if this happens there is an issue with the schema? - return '' -} - -function getEntityByLiteral( - fileEntity: string, - schema: GenericSchema, -): null | Entity { - if ( - 'entities' in schema.objects && - typeof schema.objects.entities === 'object' - ) { - const entities = schema.objects.entities - const key = Object.keys(entities).find((key) => { - return ( - hasProp(entities, key) && - hasProp(entities[key], 'name') && - entities[key].name === fileEntity - ) - }) - if (key && hasProp(entities, key)) { - return entities[key] as Entity - } - } - return null -} - -export async function entityLabelCheck( - schema: GenericSchema, - context: BIDSContext, -) { - if (!('formats' in schema.objects) || !('entities' in schema.objects)) { - throw new Error('schema missing keys') - } - const formats = schema.objects.formats as unknown as Record - const entities = schema.objects.entities as unknown as Record - Object.keys(context.entities).map((fileEntity) => { - const entity = getEntityByLiteral(fileEntity, schema) - if ( - entity && - entity.format && - typeof entity.format === 'string' && - hasProp(formats, entity.format) - ) { - // assuming all formats are well defined in schema.objects - const pattern = formats[entity.format].pattern - const rePattern = new RegExp(`^${pattern}$`) - const label = context.entities[fileEntity] - if (!rePattern.test(label)) { - context.dataset.issues.add({ - code: 'INVALID_ENTITY_LABEL', - location: context.path, - issueMessage: `entity: ${fileEntity} label: ${label} pattern: ${pattern}`, - }) - } - } else { - // unknown entity - } - }) - return Promise.resolve() -} - -const ruleChecks: RuleCheckFunction[] = [ - entityRuleIssue, - datatypeMismatch, - extensionMismatch, - invalidLocation, -] - -async function checkRules(schema: GenericSchema, context: BIDSContext) { - if (context.filenameRules.length === 1) { - for (const check of ruleChecks) { - check( - context.filenameRules[0], - schema as unknown as GenericSchema, - context, - ) - } - } else { - const ogIssues = context.dataset.issues - const noIssues: [string, DatasetIssues][] = [] - const someIssues: [string, DatasetIssues][] = [] - for (const path of context.filenameRules) { - const tempIssues = new DatasetIssues() - context.dataset.issues = tempIssues - for (const check of ruleChecks) { - check(path, schema as unknown as GenericSchema, context) - } - tempIssues.size ? someIssues.push([path, tempIssues]) : noIssues.push([path, tempIssues]) - } - if (noIssues.length) { - context.dataset.issues = ogIssues - context.filenameRules = [noIssues[0][0]] - } else if (someIssues.length) { - // What would we want to do with each rules issues? Add all? - context.dataset.issues = ogIssues - context.dataset.issues.add({ - code: 'ALL_FILENAME_RULES_HAVE_ISSUES', - location: context.path, - issueMessage: `Rules that matched with issues: ${ - someIssues - .map((x) => x[0]) - .join(', ') - }`, - }) - } - } - return Promise.resolve() -} - -function entityRuleIssue( - path: string, - schema: GenericSchema, - context: BIDSContext, -) { - const rule = schema[path] - if (!('entities' in rule)) { - if (Object.keys(context.entities).length > 0) { - // Throw issue for entity in file but not rule - } - return - } - - const fileEntities = Object.keys(context.entities) - const ruleEntities = Object.keys(rule.entities).map((key) => lookupEntityLiteral(key, schema)) - - // skip required entity checks if file is at root. - // No requirements for inherited sidecars at this level. - if (!isAtRoot(context)) { - const ruleEntitiesRequired = Object.entries(rule.entities) - .filter(([_, v]) => v === 'required') - .map(([k, _]) => lookupEntityLiteral(k, schema)) - - const missingRequired = ruleEntitiesRequired.filter( - (required) => !fileEntities.includes(required as string), - ) - - if (missingRequired.length) { - context.dataset.issues.add({ - code: 'MISSING_REQUIRED_ENTITY', - location: context.path, - issueMessage: `${missingRequired.join(', ')} missing from rule ${path}`, - rule: path, - }) - } - } - - const entityNotInRule = fileEntities.filter( - (fileEntity) => !ruleEntities.includes(fileEntity), - ) - - if (entityNotInRule.length) { - context.dataset.issues.add({ - code: 'ENTITY_NOT_IN_RULE', - location: context.path, - issueMessage: `${entityNotInRule.join(', ')} not in rule ${path}`, - rule: path, - }) - } -} - -function datatypeMismatch( - path: string, - schema: GenericSchema, - context: BIDSContext, -) { - const rule = schema[path] - if ( - !!context.datatype && - Array.isArray(rule.datatypes) && - !rule.datatypes.includes(context.datatype) - ) { - context.dataset.issues.add({ - code: 'DATATYPE_MISMATCH', - location: context.path, - issueMessage: `Datatype rule being applied: ${path}`, - rule: path, - }) - } -} - -async function extensionMismatch( - path: string, - schema: GenericSchema, - context: BIDSContext, -) { - const rule = schema[path] - if ( - Array.isArray(rule.extensions) && - !rule.extensions.includes(context.extension) - ) { - context.dataset.issues.add({ - code: 'EXTENSION_MISMATCH', - location: context.path, - rule: path, - }) - } -} - -async function invalidLocation( - path: string, - schema: GenericSchema, - context: BIDSContext, -) { - const rule = schema[path] - if (!('entities' in rule)) { - return - } - const sub: string | undefined = context.entities.sub - const ses: string | undefined = context.entities.ses - - if (sub) { - let pattern = `/sub-${sub}/` - if (ses) { - pattern += `ses-${ses}/` - } - if (!context.path.startsWith(pattern)) { - context.dataset.issues.add({ - code: 'INVALID_LOCATION', - location: context.path, - issueMessage: `Expected location: ${pattern}`, - }) - } - } - - if (!sub && context.path.match(/^\/sub-/)) { - context.dataset.issues.add({ - code: 'INVALID_LOCATION', - location: context.path, - issueMessage: `Expected location: /${context.file.name}`, - }) - } - if (!ses && context.path.match(/\/ses-/)) { - context.dataset.issues.add({ - code: 'INVALID_LOCATION', - location: context.path, - issueMessage: `Unexpected session directory`, - }) - } -} - -async function reconstructionFailure( - schema: GenericSchema, - context: BIDSContext, -) { - if (Object.keys(context.entities).length === 0) { - return - } - const typedSchema = schema as unknown as Schema - const entityKeys = typedSchema.rules.entities - .map((entity) => typedSchema.objects.entities[entity].name) - .filter((entity) => entity in context.entities) - // join with hyphen - const entities = entityKeys.map((entity) => `${entity}-${context.entities[entity]}`) - const expectedFilename = [...entities, context.suffix + context.extension].join('_') - if (context.file.name !== expectedFilename) { - context.dataset.issues.add({ - code: 'FILENAME_MISMATCH', - location: context.path, - issueMessage: `Expected filename: ${expectedFilename}`, - }) - } -} diff --git a/bids-validator/src/validators/hed.ts b/bids-validator/src/validators/hed.ts deleted file mode 100644 index cd832f960..000000000 --- a/bids-validator/src/validators/hed.ts +++ /dev/null @@ -1,120 +0,0 @@ -import hedValidator from '@hed/validator' -import { hedOldToNewLookup } from '../issues/list.ts' -import type { GenericSchema } from '../types/schema.ts' -import type { IssueFile } from '../types/issues.ts' -import type { BIDSContext, BIDSContextDataset } from '../schema/context.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import type { ColumnsMap } from '../types/columns.ts' - -function sidecarHasHed(sidecarData: BIDSContext['sidecar']) { - if (!sidecarData) { - return false - } - return Object.keys(sidecarData).some((x) => sidecarValueHasHed(sidecarData[x])) -} - -function sidecarValueHasHed(sidecarValue: unknown) { - return ( - sidecarValue !== null && - typeof sidecarValue === 'object' && - 'HED' in sidecarValue && - sidecarValue.HED !== undefined - ) -} - -let hedSchemas: object | undefined | null = undefined - -async function setHedSchemas(datasetDescriptionJson = {}) { - const datasetDescriptionData = new hedValidator.bids.BidsJsonFile( - '/dataset_description.json', - datasetDescriptionJson, - null, - ) - try { - hedSchemas = await hedValidator.bids.buildBidsSchemas( - datasetDescriptionData, - null, - ) - return [] as HedIssue[] - } catch (issueError) { - hedSchemas = null - return hedValidator.bids.BidsHedIssue.fromHedIssues( - issueError, - datasetDescriptionData.file, - ) - } -} - -export interface HedIssue { - code: number - file: IssueFile - evidence: string -} - -export async function hedValidate( - schema: GenericSchema, - context: BIDSContext, -): Promise { - let file - let hedValidationIssues = [] as HedIssue[] - - try { - if (context.extension == '.tsv' && context.columns) { - if (!('HED' in context.columns) && !sidecarHasHed(context.sidecar)) { - return - } - hedValidationIssues = await setHedSchemas(context.dataset.dataset_description) - - file = await buildHedTsvFile(context) - } else if (context.extension == '.json' && sidecarHasHed(context.json)) { - hedValidationIssues = hedValidationIssues = await setHedSchemas( - context.dataset.dataset_description, - ) - file = buildHedSidecarFile(context) - } - - if (file) { - hedValidationIssues.push(...file.validate(hedSchemas)) - } - } catch (error) { - context.dataset.issues.add({ - code: 'HED_ERROR', - location: context.path, - issueMessage: error, - }) - } - - hedValidationIssues.map((hedIssue) => { - const code = hedIssue.code - if (code in hedOldToNewLookup) { - const location = hedIssue.file ? hedIssue.file.path : '' - context.dataset.issues.add({ - code: hedOldToNewLookup[code], - // @ts-expect-error - subCode: hedIssue.hedIssue.hedCode, // Hidden property - location, - issueMessage: hedIssue.evidence, - }) - } - }) -} - -function buildHedTsvFile(context: BIDSContext) { - const eventFile = new hedValidator.bids.BidsTsvFile( - context.path, - context.columns, - context.file, - [], - context.sidecar, - ) - return eventFile -} - -function buildHedSidecarFile(context: BIDSContext) { - const sidecarFile = new hedValidator.bids.BidsSidecar( - context.path, - context.json, - context.file, - ) - return sidecarFile -} diff --git a/bids-validator/src/validators/internal/emptyFile.ts b/bids-validator/src/validators/internal/emptyFile.ts deleted file mode 100644 index 25a02fd79..000000000 --- a/bids-validator/src/validators/internal/emptyFile.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ContextCheckFunction } from '../../types/check.ts' - -// Non-schema EMPTY_FILE implementation -export const emptyFile: ContextCheckFunction = (schema, context) => { - if (context.file.size === 0) { - context.dataset.issues.add({ - code: 'EMPTY_FILE', - location: context.path, - }) - } - return Promise.resolve() -} diff --git a/bids-validator/src/validators/internal/unusedFile.ts b/bids-validator/src/validators/internal/unusedFile.ts deleted file mode 100644 index 89e0f9d54..000000000 --- a/bids-validator/src/validators/internal/unusedFile.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { GenericSchema } from '../../types/schema.ts' -import type { BIDSFile, FileTree } from '../../types/filetree.ts' -import type { BIDSContextDataset } from '../../schema/context.ts' - -function* walkFileTree(fileTree?: FileTree): Generator { - if (!fileTree) { - return - } - for (const file of fileTree.files) { - if (!file.ignored) { - yield file - } - } - for (const dir of fileTree.directories) { - if (!dir.ignored) { - yield* walkFileTree(dir) - } - } -} - -export async function unusedStimulus( - schema: GenericSchema, - dsContext: BIDSContextDataset, -) { - const stimDir = dsContext.tree.get('stimuli') as FileTree - const unusedStimuli = [...walkFileTree(stimDir)].filter((stimulus) => !stimulus.viewed) - if (unusedStimuli.length) { - dsContext.issues.add({ code: 'UNUSED_STIMULUS', affects: unusedStimuli.map((s) => s.path) }) - } -} - -const standalone_json = ['dataset_description.json', 'genetic_info.json'] - -export async function sidecarWithoutDatafile( - schema: GenericSchema, - dsContext: BIDSContextDataset, -) { - const unusedSidecars = [...walkFileTree(dsContext.tree)].filter( - (file) => (!file.viewed && file.name.endsWith('.json') && - !standalone_json.includes(file.name)), - ) - unusedSidecars.forEach((sidecar) => { - dsContext.issues.add({ code: 'SIDECAR_WITHOUT_DATAFILE', location: sidecar.path }) - }) -} diff --git a/bids-validator/src/validators/json.ts b/bids-validator/src/validators/json.ts deleted file mode 100644 index af10f0dca..000000000 --- a/bids-validator/src/validators/json.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Ajv, type JSONSchemaType, type ValidateFunction } from '@ajv' -import type { Schema } from '../types/schema.ts' -import { memoize } from '../utils/memoize.ts' -import { logger } from '../utils/logger.ts' - -const metadataValidator = new Ajv({ strictSchema: false }) - -// Bind the method to the instance before memoizing to avoid losing the context -export const compile = memoize(metadataValidator.compile.bind(metadataValidator)) - -export function setCustomMetadataFormats(schema: Schema): void { - if (typeof schema.objects.formats !== 'object') { - logger.warn( - `schema.objects.formats missing from schema, format validation disabled.`, - ) - return - } - const schemaFormats = schema.objects.formats - for (const key of Object.keys(schemaFormats)) { - const pattern = schemaFormats[key]['pattern'] - if (typeof pattern !== 'string') { - logger.warn( - `schema.objects.formats.${key} pattern missing or invalid. Skipping this format for addition to context json validator`, - ) - continue - } - metadataValidator.addFormat(key, pattern) - } -} diff --git a/bids-validator/src/validators/validateFiles.test.ts b/bids-validator/src/validators/validateFiles.test.ts deleted file mode 100644 index da28a5bed..000000000 --- a/bids-validator/src/validators/validateFiles.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { assert, assertEquals } from '@std/assert' -import { filenameIdentify } from './filenameIdentify.ts' -import { filenameValidate } from './filenameValidate.ts' -import { BIDSContext } from '../schema/context.ts' -import { loadSchema } from '../setup/loadSchema.ts' -import type { GenericSchema, Schema } from '../types/schema.ts' -import type { DatasetIssues } from '../issues/datasetIssues.ts' -import { pathToFile } from '../files/filetree.ts' - -const schema = await loadSchema() as unknown as GenericSchema - -function validatePath(path: string): DatasetIssues { - const context = new BIDSContext(pathToFile(path)) - filenameIdentify(schema, context) - filenameValidate(schema, context) - return context.dataset.issues -} - -Deno.test('test valid paths', async (t) => { - const validFiles = [ - '/README', - '/CHANGES', - '/dataset_description.json', - '/participants.tsv', - '/participants.json', - '/sub-01/sub-01_sessions.tsv', - '/sub-01/sub-01_sessions.json', - '/sub-01/sub-01_dwi.bval', - '/sub-01/sub-01_dwi.bvec', - '/sub-01/sub-01_dwi.json', - '/sub-01/sub-01_run-01_dwi.bval', - '/sub-01/sub-01_run-01_dwi.bvec', - '/sub-01/sub-01_run-01_dwi.json', - '/sub-01/sub-01_acq-singleband_dwi.bval', - '/sub-01/sub-01_acq-singleband_dwi.bvec', - '/sub-01/sub-01_acq-singleband_dwi.json', - '/sub-01/sub-01_acq-singleband_run-01_dwi.bval', - '/sub-01/sub-01_acq-singleband_run-01_dwi.bvec', - '/sub-01/sub-01_acq-singleband_run-01_dwi.json', - '/sub-01/ses-test/sub-01_ses-test_dwi.bval', - '/sub-01/ses-test/sub-01_ses-test_dwi.bvec', - '/sub-01/ses-test/sub-01_ses-test_dwi.json', - '/sub-01/ses-test/sub-01_ses-test_run-01_dwi.bval', - '/sub-01/ses-test/sub-01_ses-test_run-01_dwi.bvec', - '/sub-01/ses-test/sub-01_ses-test_run-01_dwi.json', - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_dwi.bval', - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_dwi.bvec', - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_dwi.json', - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_run-01_dwi.bval', - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_run-01_dwi.bvec', - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_run-01_dwi.json', - '/phenotype/measurement_tool_name.tsv', - '/phenotype/measurement_tool_name.json', - ] - for (const filename of validFiles) { - await t.step(filename, async () => { - const issues = validatePath(filename) - assertEquals( - issues.get({ location: filename }).length, - 0, - Deno.inspect(issues), - ) - }) - } -}) - -Deno.test('test invalid paths', async (t) => { - const invalidFiles = [ - '/RADME', // wrong filename - '/CANGES', // wrong filename - '/dataset_descrption.json', // wrong filename - '/dataset_description.jon', // wrong extension - '/participants.sv', // wrong extension - '/participnts.tsv', // wrong filename - '/particpants.json', // wrong filename - '/participants.son', // wrong extension - '/sub-02/sub-01_sessions.tsv', // wrong sub id in the filename - '/sub-01_sessions.tsv', // missed subject id dir - '/sub-01/sub-01_sesions.tsv', // wrong modality - '/sub-01/sub-01_sesions.ext', // wrong extension - '/sub-01/sub-01_sessions.jon', // wrong extension - '/sub-01/ses-ses/sub-01_dwi.bval', // redundant dir /ses-ses/ - '/sub-01/01_dwi.bvec', // missed subject suffix - '/sub-01/sub_dwi.json', // missed subject id - '/sub-01/sub-01_23_run-01_dwi.bval', // wrong _23_ - '/sub-01/sub-01_run-01_dwi.vec', // wrong extension - '/sub-01/sub-01_run-01_dwi.jsn', // wrong extension - '/sub-01/sub-01_acq_dwi.bval', // missed suffix value - '/sub-01/sub-01_acq-23-singleband_dwi.bvec', // redundant -23- - '/sub-01/anat/sub-01_acq-singleband_dwi.json', // redundant /anat/ - '/sub-01/sub-01_recrod-record_acq-singleband_run-01_dwi.bval', // redundant record-record_ - '/sub_01/sub-01_acq-singleband_run-01_dwi.bvec', // wrong /sub_01/ - '/sub-01/sub-01_acq-singleband__run-01_dwi.json', // wrong __ - '/sub-01/ses-test/sub-01_ses_test_dwi.bval', // wrong ses_test - '/sub-01/ses-test/sb-01_ses-test_dwi.bvec', // wrong sb-01 - '/sub-01/ses-test/sub-01_ses-test_dw.json', // wrong modality - '/sub-01/ses-test/sub-01_ses-test_run-01_dwi.val', // wrong extension - '/sub-01/ses-test/sub-01_run-01_dwi.bvec', // missed session in the filename - '/sub-01/ses-test/ses-test_run-01_dwi.json', // missed subject in the filename - '/sub-01/ses-test/sub-01_ses-test_acq-singleband.bval', // missed modality - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_dwi', // missed extension - '/ses-test/sub-01/sub-01_ses-test_acq_singleband_dwi.json', // wrong dirs order - '/sub-01/ses-test/sub-02_ses-test_acq-singleband_run-01_dwi.bval', // wrong sub id in the filename - '/sub-01/sub-01_ses-test_acq-singleband_run-01_dwi.bvec', // ses dir missed - '/ses-test/sub-01_ses-test_acq-singleband_run-01_dwi.json', // sub id dir missed - '/sub-01/ses-test/sub-01_ses-test_run-01_acq-singleband_dwi.json', // incorrect entity order - '/sub-01/ses-test/sub-01_ses-test_acq-singleband_acq-singleband_run-01_dwi.json', // entity appears twice - '/measurement_tool_name.tsv', // missed phenotype dir - '/phentype/measurement_tool_name.josn', // wrong phenotype dir - '/phenotype/measurement_tool_name.jsn', // wrong extension - ] - for (const filename of invalidFiles) { - await t.step(filename, async () => { - const context = new BIDSContext(pathToFile(filename)) - await filenameIdentify(schema, context) - await filenameValidate(schema, context) - assert( - context.dataset.issues.get({ - location: context.file.path, - }).length > 0, - `Matching filename rules: ${context.filenameRules}`, - ) - }) - } -}) diff --git a/bids-validator/src/version.ts b/bids-validator/src/version.ts deleted file mode 100644 index 10b25b372..000000000 --- a/bids-validator/src/version.ts +++ /dev/null @@ -1,50 +0,0 @@ -import gitmeta from './.git-meta.json' with { type: 'json' } -import denojson from '../deno.json' with { type: 'json' } -import { dirname } from '@std/path' - -/** - * Determine the version of the currently running script. - * - * The version is determined by the following rules: - * - * 1. Search for a hard-coded version populated by git-archive or the build. - * 2. If the script is running from a local file, the version is determined by - * the output of `git describe --tags --always` in the script's directory. - * 3. Fall back to the static metadata in `deno.json`, which should correspond - * to the most recent dev tag. - * - * @returns The version of the script. - */ -export async function getVersion(): Promise { - // Hard-coded JSON wins - let version = getArchiveVersion() - if (version) return version - - // Local git repository - const url = new URL(Deno.mainModule) - if (url.protocol === 'file:') { - version = await getLocalVersion(dirname(url.pathname)) - if (version) return version - } - - // Fall back to static metadata - return denojson.version -} - -async function getLocalVersion(path: string): Promise { - const p = Deno.run({ - // safe.directory setting so we could still operate from another user - cmd: ['git', '-C', path, '-c', 'safe.directory=*', 'describe', '--tags', '--always'], - stdout: 'piped', - }) - const description = new TextDecoder().decode(await p.output()).trim() - p.close() - return description -} - -function getArchiveVersion(): string | undefined { - if (!gitmeta.description.startsWith('$Format:')) { - return gitmeta.description - } - return undefined -} diff --git a/deno.json b/deno.json deleted file mode 100644 index 358b7d299..000000000 --- a/deno.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "lock": false -} diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index 289e710c0..000000000 --- a/web/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -.vite -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/web/README.md b/web/README.md deleted file mode 100644 index e083fb7e8..000000000 --- a/web/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# BIDS Validator web app - -This is a web app for running the schema based BIDS validator in the browser. For local development, this is run using deno and uses an esbuild bundle created from the native deno codebase. A bundle is generated in `dist/validator` and used by the otherwise static vite app. - -## Running - -You need to have Deno v1.28.0 or later installed to run this repo. - -Start a dev server: - -``` -$ deno task dev -``` - -## Deploy - -Build production assets: - -``` -$ deno task build -``` - -## Notes - -- You need to use `.mjs` or `.mts` extension for the `vite.config.[ext]` file. - -## Papercuts - -Currently there's a "papercut" for Deno users: - -- peer dependencies need to be referenced in `vite.config.js` - in this example - it is `react` and `react-dom` packages that need to be referenced diff --git a/web/deno.json b/web/deno.json deleted file mode 100644 index e16770c87..000000000 --- a/web/deno.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "dev": "../bids-validator/build.ts && deno run -A --node-modules-dir npm:vite", - "build": "../bids-validator/build.ts && deno run -A --node-modules-dir npm:vite build", - "preview": "deno run -A --node-modules-dir npm:vite preview", - "serve": "deno run --allow-net --allow-read https://deno.land/std@0.157.0/http/file_server.ts dist/" - } -} \ No newline at end of file diff --git a/web/deno.lock b/web/deno.lock deleted file mode 100644 index cfa5ae4cd..000000000 --- a/web/deno.lock +++ /dev/null @@ -1,2195 +0,0 @@ -{ - "version": "3", - "packages": { - "specifiers": { - "npm:@vitejs/plugin-react@^4.2.1": "npm:@vitejs/plugin-react@4.2.1_vite@5.1.4_@babel+core@7.24.0", - "npm:create-vite-extra@latest": "npm:create-vite-extra@2.1.1", - "npm:ignore": "npm:ignore@5.3.0", - "npm:ignore@^5.2.4": "npm:ignore@5.3.0", - "npm:react-dom@^18.2.0": "npm:react-dom@18.2.0_react@18.2.0", - "npm:react@^18.2.0": "npm:react@18.2.0", - "npm:vite": "npm:vite@5.1.4", - "npm:vite-plugin-https-imports": "npm:vite-plugin-https-imports@0.1.0", - "npm:vite-plugin-https-imports@0.1.0": "npm:vite-plugin-https-imports@0.1.0", - "npm:vite-plugin-node-polyfills@0.22.0": "npm:vite-plugin-node-polyfills@0.22.0_vite@5.1.4", - "npm:vite@^5.0.10": "npm:vite@5.1.4" - }, - "npm": { - "@ampproject/remapping@2.2.1": { - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dependencies": { - "@jridgewell/gen-mapping": "@jridgewell/gen-mapping@0.3.4", - "@jridgewell/trace-mapping": "@jridgewell/trace-mapping@0.3.23" - } - }, - "@babel/code-frame@7.23.5": { - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dependencies": { - "@babel/highlight": "@babel/highlight@7.23.4", - "chalk": "chalk@2.4.2" - } - }, - "@babel/compat-data@7.23.5": { - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dependencies": {} - }, - "@babel/core@7.24.0": { - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", - "dependencies": { - "@ampproject/remapping": "@ampproject/remapping@2.2.1", - "@babel/code-frame": "@babel/code-frame@7.23.5", - "@babel/generator": "@babel/generator@7.23.6", - "@babel/helper-compilation-targets": "@babel/helper-compilation-targets@7.23.6", - "@babel/helper-module-transforms": "@babel/helper-module-transforms@7.23.3_@babel+core@7.24.0", - "@babel/helpers": "@babel/helpers@7.24.0", - "@babel/parser": "@babel/parser@7.24.0", - "@babel/template": "@babel/template@7.24.0", - "@babel/traverse": "@babel/traverse@7.24.0", - "@babel/types": "@babel/types@7.24.0", - "convert-source-map": "convert-source-map@2.0.0", - "debug": "debug@4.3.4", - "gensync": "gensync@1.0.0-beta.2", - "json5": "json5@2.2.3", - "semver": "semver@6.3.1" - } - }, - "@babel/generator@7.23.6": { - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0", - "@jridgewell/gen-mapping": "@jridgewell/gen-mapping@0.3.4", - "@jridgewell/trace-mapping": "@jridgewell/trace-mapping@0.3.23", - "jsesc": "jsesc@2.5.2" - } - }, - "@babel/helper-compilation-targets@7.23.6": { - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dependencies": { - "@babel/compat-data": "@babel/compat-data@7.23.5", - "@babel/helper-validator-option": "@babel/helper-validator-option@7.23.5", - "browserslist": "browserslist@4.23.0", - "lru-cache": "lru-cache@5.1.1", - "semver": "semver@6.3.1" - } - }, - "@babel/helper-environment-visitor@7.22.20": { - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dependencies": {} - }, - "@babel/helper-function-name@7.23.0": { - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "@babel/template@7.24.0", - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/helper-hoist-variables@7.22.5": { - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/helper-module-imports@7.22.15": { - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/helper-module-transforms@7.23.3_@babel+core@7.24.0": { - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dependencies": { - "@babel/core": "@babel/core@7.24.0", - "@babel/helper-environment-visitor": "@babel/helper-environment-visitor@7.22.20", - "@babel/helper-module-imports": "@babel/helper-module-imports@7.22.15", - "@babel/helper-simple-access": "@babel/helper-simple-access@7.22.5", - "@babel/helper-split-export-declaration": "@babel/helper-split-export-declaration@7.22.6", - "@babel/helper-validator-identifier": "@babel/helper-validator-identifier@7.22.20" - } - }, - "@babel/helper-plugin-utils@7.24.0": { - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", - "dependencies": {} - }, - "@babel/helper-simple-access@7.22.5": { - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/helper-split-export-declaration@7.22.6": { - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/helper-string-parser@7.23.4": { - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dependencies": {} - }, - "@babel/helper-validator-identifier@7.22.20": { - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dependencies": {} - }, - "@babel/helper-validator-option@7.23.5": { - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dependencies": {} - }, - "@babel/helpers@7.24.0": { - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", - "dependencies": { - "@babel/template": "@babel/template@7.24.0", - "@babel/traverse": "@babel/traverse@7.24.0", - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/highlight@7.23.4": { - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dependencies": { - "@babel/helper-validator-identifier": "@babel/helper-validator-identifier@7.22.20", - "chalk": "chalk@2.4.2", - "js-tokens": "js-tokens@4.0.0" - } - }, - "@babel/parser@7.24.0": { - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", - "dependencies": {} - }, - "@babel/plugin-transform-react-jsx-self@7.23.3_@babel+core@7.24.0": { - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", - "dependencies": { - "@babel/core": "@babel/core@7.24.0", - "@babel/helper-plugin-utils": "@babel/helper-plugin-utils@7.24.0" - } - }, - "@babel/plugin-transform-react-jsx-source@7.23.3_@babel+core@7.24.0": { - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", - "dependencies": { - "@babel/core": "@babel/core@7.24.0", - "@babel/helper-plugin-utils": "@babel/helper-plugin-utils@7.24.0" - } - }, - "@babel/template@7.24.0": { - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", - "dependencies": { - "@babel/code-frame": "@babel/code-frame@7.23.5", - "@babel/parser": "@babel/parser@7.24.0", - "@babel/types": "@babel/types@7.24.0" - } - }, - "@babel/traverse@7.24.0": { - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", - "dependencies": { - "@babel/code-frame": "@babel/code-frame@7.23.5", - "@babel/generator": "@babel/generator@7.23.6", - "@babel/helper-environment-visitor": "@babel/helper-environment-visitor@7.22.20", - "@babel/helper-function-name": "@babel/helper-function-name@7.23.0", - "@babel/helper-hoist-variables": "@babel/helper-hoist-variables@7.22.5", - "@babel/helper-split-export-declaration": "@babel/helper-split-export-declaration@7.22.6", - "@babel/parser": "@babel/parser@7.24.0", - "@babel/types": "@babel/types@7.24.0", - "debug": "debug@4.3.4", - "globals": "globals@11.12.0" - } - }, - "@babel/types@7.24.0": { - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", - "dependencies": { - "@babel/helper-string-parser": "@babel/helper-string-parser@7.23.4", - "@babel/helper-validator-identifier": "@babel/helper-validator-identifier@7.22.20", - "to-fast-properties": "to-fast-properties@2.0.0" - } - }, - "@esbuild/aix-ppc64@0.19.12": { - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "dependencies": {} - }, - "@esbuild/android-arm64@0.19.12": { - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "dependencies": {} - }, - "@esbuild/android-arm@0.19.12": { - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "dependencies": {} - }, - "@esbuild/android-x64@0.19.12": { - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "dependencies": {} - }, - "@esbuild/darwin-arm64@0.19.12": { - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "dependencies": {} - }, - "@esbuild/darwin-x64@0.19.12": { - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "dependencies": {} - }, - "@esbuild/freebsd-arm64@0.19.12": { - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "dependencies": {} - }, - "@esbuild/freebsd-x64@0.19.12": { - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "dependencies": {} - }, - "@esbuild/linux-arm64@0.19.12": { - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "dependencies": {} - }, - "@esbuild/linux-arm@0.19.12": { - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "dependencies": {} - }, - "@esbuild/linux-ia32@0.19.12": { - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "dependencies": {} - }, - "@esbuild/linux-loong64@0.19.12": { - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "dependencies": {} - }, - "@esbuild/linux-mips64el@0.19.12": { - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "dependencies": {} - }, - "@esbuild/linux-ppc64@0.19.12": { - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "dependencies": {} - }, - "@esbuild/linux-riscv64@0.19.12": { - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "dependencies": {} - }, - "@esbuild/linux-s390x@0.19.12": { - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "dependencies": {} - }, - "@esbuild/linux-x64@0.19.12": { - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "dependencies": {} - }, - "@esbuild/netbsd-x64@0.19.12": { - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "dependencies": {} - }, - "@esbuild/openbsd-x64@0.19.12": { - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "dependencies": {} - }, - "@esbuild/sunos-x64@0.19.12": { - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "dependencies": {} - }, - "@esbuild/win32-arm64@0.19.12": { - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "dependencies": {} - }, - "@esbuild/win32-ia32@0.19.12": { - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "dependencies": {} - }, - "@esbuild/win32-x64@0.19.12": { - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "dependencies": {} - }, - "@jridgewell/gen-mapping@0.3.4": { - "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", - "dependencies": { - "@jridgewell/set-array": "@jridgewell/set-array@1.2.1", - "@jridgewell/sourcemap-codec": "@jridgewell/sourcemap-codec@1.4.15", - "@jridgewell/trace-mapping": "@jridgewell/trace-mapping@0.3.23" - } - }, - "@jridgewell/resolve-uri@3.1.2": { - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dependencies": {} - }, - "@jridgewell/set-array@1.2.1": { - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dependencies": {} - }, - "@jridgewell/sourcemap-codec@1.4.15": { - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dependencies": {} - }, - "@jridgewell/sourcemap-codec@1.5.0": { - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dependencies": {} - }, - "@jridgewell/trace-mapping@0.3.23": { - "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", - "dependencies": { - "@jridgewell/resolve-uri": "@jridgewell/resolve-uri@3.1.2", - "@jridgewell/sourcemap-codec": "@jridgewell/sourcemap-codec@1.4.15" - } - }, - "@rollup/plugin-inject@5.0.5": { - "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", - "dependencies": { - "@rollup/pluginutils": "@rollup/pluginutils@5.1.0", - "estree-walker": "estree-walker@2.0.2", - "magic-string": "magic-string@0.30.11" - } - }, - "@rollup/pluginutils@5.1.0": { - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", - "dependencies": { - "@types/estree": "@types/estree@1.0.5", - "estree-walker": "estree-walker@2.0.2", - "picomatch": "picomatch@2.3.1" - } - }, - "@rollup/rollup-android-arm-eabi@4.12.0": { - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", - "dependencies": {} - }, - "@rollup/rollup-android-arm64@4.12.0": { - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", - "dependencies": {} - }, - "@rollup/rollup-darwin-arm64@4.12.0": { - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", - "dependencies": {} - }, - "@rollup/rollup-darwin-x64@4.12.0": { - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", - "dependencies": {} - }, - "@rollup/rollup-linux-arm-gnueabihf@4.12.0": { - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", - "dependencies": {} - }, - "@rollup/rollup-linux-arm64-gnu@4.12.0": { - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", - "dependencies": {} - }, - "@rollup/rollup-linux-arm64-musl@4.12.0": { - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", - "dependencies": {} - }, - "@rollup/rollup-linux-riscv64-gnu@4.12.0": { - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", - "dependencies": {} - }, - "@rollup/rollup-linux-x64-gnu@4.12.0": { - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", - "dependencies": {} - }, - "@rollup/rollup-linux-x64-musl@4.12.0": { - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", - "dependencies": {} - }, - "@rollup/rollup-win32-arm64-msvc@4.12.0": { - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", - "dependencies": {} - }, - "@rollup/rollup-win32-ia32-msvc@4.12.0": { - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", - "dependencies": {} - }, - "@rollup/rollup-win32-x64-msvc@4.12.0": { - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", - "dependencies": {} - }, - "@types/babel__core@7.20.5": { - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dependencies": { - "@babel/parser": "@babel/parser@7.24.0", - "@babel/types": "@babel/types@7.24.0", - "@types/babel__generator": "@types/babel__generator@7.6.8", - "@types/babel__template": "@types/babel__template@7.4.4", - "@types/babel__traverse": "@types/babel__traverse@7.20.5" - } - }, - "@types/babel__generator@7.6.8": { - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0" - } - }, - "@types/babel__template@7.4.4": { - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dependencies": { - "@babel/parser": "@babel/parser@7.24.0", - "@babel/types": "@babel/types@7.24.0" - } - }, - "@types/babel__traverse@7.20.5": { - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", - "dependencies": { - "@babel/types": "@babel/types@7.24.0" - } - }, - "@types/estree@1.0.5": { - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dependencies": {} - }, - "@vitejs/plugin-react@4.2.1_vite@5.1.4_@babel+core@7.24.0": { - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", - "dependencies": { - "@babel/core": "@babel/core@7.24.0", - "@babel/plugin-transform-react-jsx-self": "@babel/plugin-transform-react-jsx-self@7.23.3_@babel+core@7.24.0", - "@babel/plugin-transform-react-jsx-source": "@babel/plugin-transform-react-jsx-source@7.23.3_@babel+core@7.24.0", - "@types/babel__core": "@types/babel__core@7.20.5", - "react-refresh": "react-refresh@0.14.0", - "vite": "vite@5.1.4" - } - }, - "ansi-regex@6.0.1": { - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dependencies": {} - }, - "ansi-styles@3.2.1": { - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "color-convert@1.9.3" - } - }, - "asn1.js@4.10.1": { - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dependencies": { - "bn.js": "bn.js@4.12.0", - "inherits": "inherits@2.0.4", - "minimalistic-assert": "minimalistic-assert@1.0.1" - } - }, - "assert@2.1.0": { - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "is-nan": "is-nan@1.3.2", - "object-is": "object-is@1.1.6", - "object.assign": "object.assign@4.1.5", - "util": "util@0.12.5" - } - }, - "asynckit@0.4.0": { - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dependencies": {} - }, - "available-typed-arrays@1.0.7": { - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dependencies": { - "possible-typed-array-names": "possible-typed-array-names@1.0.0" - } - }, - "axios@1.6.7": { - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", - "dependencies": { - "follow-redirects": "follow-redirects@1.15.5", - "form-data": "form-data@4.0.0", - "proxy-from-env": "proxy-from-env@1.1.0" - } - }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dependencies": {} - }, - "base64-js@1.5.1": { - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dependencies": {} - }, - "bl@5.1.0": { - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "buffer@6.0.3", - "inherits": "inherits@2.0.4", - "readable-stream": "readable-stream@3.6.2" - } - }, - "bn.js@4.12.0": { - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dependencies": {} - }, - "bn.js@5.2.1": { - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dependencies": {} - }, - "brace-expansion@2.0.1": { - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "balanced-match@1.0.2" - } - }, - "brorand@1.1.0": { - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dependencies": {} - }, - "browser-resolve@2.0.0": { - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "dependencies": { - "resolve": "resolve@1.22.8" - } - }, - "browserify-aes@1.2.0": { - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dependencies": { - "buffer-xor": "buffer-xor@1.0.3", - "cipher-base": "cipher-base@1.0.4", - "create-hash": "create-hash@1.2.0", - "evp_bytestokey": "evp_bytestokey@1.0.3", - "inherits": "inherits@2.0.4", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "browserify-cipher@1.0.1": { - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dependencies": { - "browserify-aes": "browserify-aes@1.2.0", - "browserify-des": "browserify-des@1.0.2", - "evp_bytestokey": "evp_bytestokey@1.0.3" - } - }, - "browserify-des@1.0.2": { - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dependencies": { - "cipher-base": "cipher-base@1.0.4", - "des.js": "des.js@1.1.0", - "inherits": "inherits@2.0.4", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "browserify-rsa@4.1.0": { - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dependencies": { - "bn.js": "bn.js@5.2.1", - "randombytes": "randombytes@2.1.0" - } - }, - "browserify-sign@4.2.3": { - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", - "dependencies": { - "bn.js": "bn.js@5.2.1", - "browserify-rsa": "browserify-rsa@4.1.0", - "create-hash": "create-hash@1.2.0", - "create-hmac": "create-hmac@1.1.7", - "elliptic": "elliptic@6.5.6", - "hash-base": "hash-base@3.0.4", - "inherits": "inherits@2.0.4", - "parse-asn1": "parse-asn1@5.1.7", - "readable-stream": "readable-stream@2.3.8", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "browserify-zlib@0.2.0": { - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dependencies": { - "pako": "pako@1.0.11" - } - }, - "browserslist@4.23.0": { - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "dependencies": { - "caniuse-lite": "caniuse-lite@1.0.30001591", - "electron-to-chromium": "electron-to-chromium@1.4.688", - "node-releases": "node-releases@2.0.14", - "update-browserslist-db": "update-browserslist-db@1.0.13_browserslist@4.23.0" - } - }, - "buffer-xor@1.0.3": { - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dependencies": {} - }, - "buffer@5.7.1": { - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dependencies": { - "base64-js": "base64-js@1.5.1", - "ieee754": "ieee754@1.2.1" - } - }, - "buffer@6.0.3": { - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dependencies": { - "base64-js": "base64-js@1.5.1", - "ieee754": "ieee754@1.2.1" - } - }, - "builtin-status-codes@3.0.0": { - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dependencies": {} - }, - "call-bind@1.0.7": { - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "es-define-property@1.0.0", - "es-errors": "es-errors@1.3.0", - "function-bind": "function-bind@1.1.2", - "get-intrinsic": "get-intrinsic@1.2.4", - "set-function-length": "set-function-length@1.2.2" - } - }, - "callsites@3.1.0": { - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dependencies": {} - }, - "caniuse-lite@1.0.30001591": { - "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", - "dependencies": {} - }, - "chalk@2.4.2": { - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "ansi-styles@3.2.1", - "escape-string-regexp": "escape-string-regexp@1.0.5", - "supports-color": "supports-color@5.5.0" - } - }, - "chalk@5.3.0": { - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dependencies": {} - }, - "cipher-base@1.0.4": { - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dependencies": { - "inherits": "inherits@2.0.4", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "cli-cursor@4.0.0": { - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dependencies": { - "restore-cursor": "restore-cursor@4.0.0" - } - }, - "cli-spinners@2.9.2": { - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dependencies": {} - }, - "color-convert@1.9.3": { - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "color-name@1.1.3" - } - }, - "color-name@1.1.3": { - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dependencies": {} - }, - "combined-stream@1.0.8": { - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "delayed-stream@1.0.0" - } - }, - "console-browserify@1.2.0": { - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dependencies": {} - }, - "constants-browserify@1.0.0": { - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dependencies": {} - }, - "convert-source-map@2.0.0": { - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dependencies": {} - }, - "core-util-is@1.0.3": { - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dependencies": {} - }, - "create-ecdh@4.0.4": { - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dependencies": { - "bn.js": "bn.js@4.12.0", - "elliptic": "elliptic@6.5.6" - } - }, - "create-hash@1.2.0": { - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dependencies": { - "cipher-base": "cipher-base@1.0.4", - "inherits": "inherits@2.0.4", - "md5.js": "md5.js@1.3.5", - "ripemd160": "ripemd160@2.0.2", - "sha.js": "sha.js@2.4.11" - } - }, - "create-hmac@1.1.7": { - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dependencies": { - "cipher-base": "cipher-base@1.0.4", - "create-hash": "create-hash@1.2.0", - "inherits": "inherits@2.0.4", - "ripemd160": "ripemd160@2.0.2", - "safe-buffer": "safe-buffer@5.2.1", - "sha.js": "sha.js@2.4.11" - } - }, - "create-require@1.1.1": { - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dependencies": {} - }, - "create-vite-extra@2.1.1": { - "integrity": "sha512-aU/CvpUg3vzUaPoB0U4LlUhGx/bmwwfBdd9IF08+i/pcTgwu5I00XlDeM1VpiTB07o3m80rejOAqAoiIxPvWdw==", - "dependencies": { - "kolorist": "kolorist@1.8.0", - "minimist": "minimist@1.2.8", - "prompts": "prompts@2.4.2" - } - }, - "crypto-browserify@3.12.0": { - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dependencies": { - "browserify-cipher": "browserify-cipher@1.0.1", - "browserify-sign": "browserify-sign@4.2.3", - "create-ecdh": "create-ecdh@4.0.4", - "create-hash": "create-hash@1.2.0", - "create-hmac": "create-hmac@1.1.7", - "diffie-hellman": "diffie-hellman@5.0.3", - "inherits": "inherits@2.0.4", - "pbkdf2": "pbkdf2@3.1.2", - "public-encrypt": "public-encrypt@4.0.3", - "randombytes": "randombytes@2.1.0", - "randomfill": "randomfill@1.0.4" - } - }, - "debug@4.3.4": { - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "ms@2.1.2" - } - }, - "define-data-property@1.1.4": { - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "es-define-property@1.0.0", - "es-errors": "es-errors@1.3.0", - "gopd": "gopd@1.0.1" - } - }, - "define-properties@1.2.1": { - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dependencies": { - "define-data-property": "define-data-property@1.1.4", - "has-property-descriptors": "has-property-descriptors@1.0.2", - "object-keys": "object-keys@1.1.1" - } - }, - "delayed-stream@1.0.0": { - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dependencies": {} - }, - "des.js@1.1.0": { - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dependencies": { - "inherits": "inherits@2.0.4", - "minimalistic-assert": "minimalistic-assert@1.0.1" - } - }, - "diffie-hellman@5.0.3": { - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dependencies": { - "bn.js": "bn.js@4.12.0", - "miller-rabin": "miller-rabin@4.0.1", - "randombytes": "randombytes@2.1.0" - } - }, - "domain-browser@4.23.0": { - "integrity": "sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==", - "dependencies": {} - }, - "eastasianwidth@0.2.0": { - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dependencies": {} - }, - "electron-to-chromium@1.4.688": { - "integrity": "sha512-3/tHg2ChPF00eukURIB8cSVt3/9oeS1oTUIEt3ivngBInUaEcBhG2VdyEDejhwQdR6SLqaiEAEc0dHS0V52pOw==", - "dependencies": {} - }, - "elliptic@6.5.6": { - "integrity": "sha512-mpzdtpeCLuS3BmE3pO3Cpp5bbjlOPY2Q0PgoF+Od1XZrHLYI28Xe3ossCmYCQt11FQKEYd9+PF8jymTvtWJSHQ==", - "dependencies": { - "bn.js": "bn.js@4.12.0", - "brorand": "brorand@1.1.0", - "hash.js": "hash.js@1.1.7", - "hmac-drbg": "hmac-drbg@1.0.1", - "inherits": "inherits@2.0.4", - "minimalistic-assert": "minimalistic-assert@1.0.1", - "minimalistic-crypto-utils": "minimalistic-crypto-utils@1.0.1" - } - }, - "emoji-regex@10.3.0": { - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dependencies": {} - }, - "es-define-property@1.0.0": { - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "get-intrinsic@1.2.4" - } - }, - "es-errors@1.3.0": { - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dependencies": {} - }, - "esbuild@0.19.12": { - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dependencies": { - "@esbuild/aix-ppc64": "@esbuild/aix-ppc64@0.19.12", - "@esbuild/android-arm": "@esbuild/android-arm@0.19.12", - "@esbuild/android-arm64": "@esbuild/android-arm64@0.19.12", - "@esbuild/android-x64": "@esbuild/android-x64@0.19.12", - "@esbuild/darwin-arm64": "@esbuild/darwin-arm64@0.19.12", - "@esbuild/darwin-x64": "@esbuild/darwin-x64@0.19.12", - "@esbuild/freebsd-arm64": "@esbuild/freebsd-arm64@0.19.12", - "@esbuild/freebsd-x64": "@esbuild/freebsd-x64@0.19.12", - "@esbuild/linux-arm": "@esbuild/linux-arm@0.19.12", - "@esbuild/linux-arm64": "@esbuild/linux-arm64@0.19.12", - "@esbuild/linux-ia32": "@esbuild/linux-ia32@0.19.12", - "@esbuild/linux-loong64": "@esbuild/linux-loong64@0.19.12", - "@esbuild/linux-mips64el": "@esbuild/linux-mips64el@0.19.12", - "@esbuild/linux-ppc64": "@esbuild/linux-ppc64@0.19.12", - "@esbuild/linux-riscv64": "@esbuild/linux-riscv64@0.19.12", - "@esbuild/linux-s390x": "@esbuild/linux-s390x@0.19.12", - "@esbuild/linux-x64": "@esbuild/linux-x64@0.19.12", - "@esbuild/netbsd-x64": "@esbuild/netbsd-x64@0.19.12", - "@esbuild/openbsd-x64": "@esbuild/openbsd-x64@0.19.12", - "@esbuild/sunos-x64": "@esbuild/sunos-x64@0.19.12", - "@esbuild/win32-arm64": "@esbuild/win32-arm64@0.19.12", - "@esbuild/win32-ia32": "@esbuild/win32-ia32@0.19.12", - "@esbuild/win32-x64": "@esbuild/win32-x64@0.19.12" - } - }, - "escalade@3.1.2": { - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dependencies": {} - }, - "escape-string-regexp@1.0.5": { - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dependencies": {} - }, - "estree-walker@2.0.2": { - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dependencies": {} - }, - "events@3.3.0": { - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dependencies": {} - }, - "evp_bytestokey@1.0.3": { - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dependencies": { - "md5.js": "md5.js@1.3.5", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "find-up@5.0.0": { - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "locate-path@6.0.0", - "path-exists": "path-exists@4.0.0" - } - }, - "follow-redirects@1.15.5": { - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "dependencies": {} - }, - "for-each@0.3.3": { - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "is-callable@1.2.7" - } - }, - "form-data@4.0.0": { - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "asynckit@0.4.0", - "combined-stream": "combined-stream@1.0.8", - "mime-types": "mime-types@2.1.35" - } - }, - "fsevents@2.3.3": { - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dependencies": {} - }, - "function-bind@1.1.2": { - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dependencies": {} - }, - "gensync@1.0.0-beta.2": { - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dependencies": {} - }, - "get-intrinsic@1.2.4": { - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "es-errors@1.3.0", - "function-bind": "function-bind@1.1.2", - "has-proto": "has-proto@1.0.3", - "has-symbols": "has-symbols@1.0.3", - "hasown": "hasown@2.0.2" - } - }, - "globals@11.12.0": { - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dependencies": {} - }, - "gopd@1.0.1": { - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "get-intrinsic@1.2.4" - } - }, - "has-flag@3.0.0": { - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dependencies": {} - }, - "has-property-descriptors@1.0.2": { - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "es-define-property@1.0.0" - } - }, - "has-proto@1.0.3": { - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dependencies": {} - }, - "has-symbols@1.0.3": { - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dependencies": {} - }, - "has-tostringtag@1.0.2": { - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "has-symbols@1.0.3" - } - }, - "hash-base@3.0.4": { - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", - "dependencies": { - "inherits": "inherits@2.0.4", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "hash.js@1.1.7": { - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "inherits@2.0.4", - "minimalistic-assert": "minimalistic-assert@1.0.1" - } - }, - "hasown@2.0.2": { - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "function-bind@1.1.2" - } - }, - "hmac-drbg@1.0.1": { - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "hash.js@1.1.7", - "minimalistic-assert": "minimalistic-assert@1.0.1", - "minimalistic-crypto-utils": "minimalistic-crypto-utils@1.0.1" - } - }, - "https-browserify@1.0.0": { - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "dependencies": {} - }, - "ieee754@1.2.1": { - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dependencies": {} - }, - "ignore@5.3.0": { - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dependencies": {} - }, - "inclusion@1.0.1": { - "integrity": "sha512-TRicJXpIfJN+a47xxjs5nfy2V5l413e4aAtsLYRG+OsDM3A3uloBd/+fDmj23RVuIL9VQfwtb37iIc0rtMw9KA==", - "dependencies": { - "parent-module": "parent-module@2.0.0" - } - }, - "inherits@2.0.4": { - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dependencies": {} - }, - "is-arguments@1.1.1": { - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-callable@1.2.7": { - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dependencies": {} - }, - "is-core-module@2.15.0": { - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", - "dependencies": { - "hasown": "hasown@2.0.2" - } - }, - "is-generator-function@1.0.10": { - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "is-interactive@2.0.0": { - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dependencies": {} - }, - "is-nan@1.3.2": { - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1" - } - }, - "is-typed-array@1.1.13": { - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dependencies": { - "which-typed-array": "which-typed-array@1.1.15" - } - }, - "is-unicode-supported@1.3.0": { - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dependencies": {} - }, - "isarray@1.0.0": { - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dependencies": {} - }, - "isomorphic-timers-promises@1.0.1": { - "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", - "dependencies": {} - }, - "js-tokens@4.0.0": { - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dependencies": {} - }, - "jsesc@2.5.2": { - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dependencies": {} - }, - "json5@2.2.3": { - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dependencies": {} - }, - "kleur@3.0.3": { - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dependencies": {} - }, - "kolorist@1.8.0": { - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dependencies": {} - }, - "locate-path@6.0.0": { - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "p-locate@5.0.0" - } - }, - "log-symbols@5.1.0": { - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dependencies": { - "chalk": "chalk@5.3.0", - "is-unicode-supported": "is-unicode-supported@1.3.0" - } - }, - "loose-envify@1.4.0": { - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "js-tokens@4.0.0" - } - }, - "lru-cache@5.1.1": { - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "yallist@3.1.1" - } - }, - "magic-string@0.30.11": { - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", - "dependencies": { - "@jridgewell/sourcemap-codec": "@jridgewell/sourcemap-codec@1.5.0" - } - }, - "md5.js@1.3.5": { - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dependencies": { - "hash-base": "hash-base@3.0.4", - "inherits": "inherits@2.0.4", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "miller-rabin@4.0.1": { - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dependencies": { - "bn.js": "bn.js@4.12.0", - "brorand": "brorand@1.1.0" - } - }, - "mime-db@1.52.0": { - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dependencies": {} - }, - "mime-types@2.1.35": { - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "mime-db@1.52.0" - } - }, - "mimic-fn@2.1.0": { - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dependencies": {} - }, - "minimalistic-assert@1.0.1": { - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dependencies": {} - }, - "minimalistic-crypto-utils@1.0.1": { - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dependencies": {} - }, - "minimatch@9.0.3": { - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dependencies": { - "brace-expansion": "brace-expansion@2.0.1" - } - }, - "minimist@1.2.8": { - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dependencies": {} - }, - "ms@2.1.2": { - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dependencies": {} - }, - "nanoid@3.3.7": { - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dependencies": {} - }, - "node-releases@2.0.14": { - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dependencies": {} - }, - "node-stdlib-browser@1.2.0": { - "integrity": "sha512-VSjFxUhRhkyed8AtLwSCkMrJRfQ3e2lGtG3sP6FEgaLKBBbxM/dLfjRe1+iLhjvyLFW3tBQ8+c0pcOtXGbAZJg==", - "dependencies": { - "assert": "assert@2.1.0", - "browser-resolve": "browser-resolve@2.0.0", - "browserify-zlib": "browserify-zlib@0.2.0", - "buffer": "buffer@5.7.1", - "console-browserify": "console-browserify@1.2.0", - "constants-browserify": "constants-browserify@1.0.0", - "create-require": "create-require@1.1.1", - "crypto-browserify": "crypto-browserify@3.12.0", - "domain-browser": "domain-browser@4.23.0", - "events": "events@3.3.0", - "https-browserify": "https-browserify@1.0.0", - "isomorphic-timers-promises": "isomorphic-timers-promises@1.0.1", - "os-browserify": "os-browserify@0.3.0", - "path-browserify": "path-browserify@1.0.1", - "pkg-dir": "pkg-dir@5.0.0", - "process": "process@0.11.10", - "punycode": "punycode@1.4.1", - "querystring-es3": "querystring-es3@0.2.1", - "readable-stream": "readable-stream@3.6.2", - "stream-browserify": "stream-browserify@3.0.0", - "stream-http": "stream-http@3.2.0", - "string_decoder": "string_decoder@1.3.0", - "timers-browserify": "timers-browserify@2.0.12", - "tty-browserify": "tty-browserify@0.0.1", - "url": "url@0.11.4", - "util": "util@0.12.5", - "vm-browserify": "vm-browserify@1.1.2" - } - }, - "object-inspect@1.13.2": { - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dependencies": {} - }, - "object-is@1.1.6": { - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1" - } - }, - "object-keys@1.1.1": { - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dependencies": {} - }, - "object.assign@4.1.5": { - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "define-properties": "define-properties@1.2.1", - "has-symbols": "has-symbols@1.0.3", - "object-keys": "object-keys@1.1.1" - } - }, - "onetime@5.1.2": { - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "mimic-fn@2.1.0" - } - }, - "ora@7.0.1": { - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dependencies": { - "chalk": "chalk@5.3.0", - "cli-cursor": "cli-cursor@4.0.0", - "cli-spinners": "cli-spinners@2.9.2", - "is-interactive": "is-interactive@2.0.0", - "is-unicode-supported": "is-unicode-supported@1.3.0", - "log-symbols": "log-symbols@5.1.0", - "stdin-discarder": "stdin-discarder@0.1.0", - "string-width": "string-width@6.1.0", - "strip-ansi": "strip-ansi@7.1.0" - } - }, - "os-browserify@0.3.0": { - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dependencies": {} - }, - "p-limit@3.1.0": { - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "yocto-queue@0.1.0" - } - }, - "p-locate@5.0.0": { - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "p-limit@3.1.0" - } - }, - "pako@1.0.11": { - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dependencies": {} - }, - "parent-module@2.0.0": { - "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", - "dependencies": { - "callsites": "callsites@3.1.0" - } - }, - "parse-asn1@5.1.7": { - "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", - "dependencies": { - "asn1.js": "asn1.js@4.10.1", - "browserify-aes": "browserify-aes@1.2.0", - "evp_bytestokey": "evp_bytestokey@1.0.3", - "hash-base": "hash-base@3.0.4", - "pbkdf2": "pbkdf2@3.1.2", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "path-browserify@1.0.1": { - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dependencies": {} - }, - "path-exists@4.0.0": { - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dependencies": {} - }, - "path-parse@1.0.7": { - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dependencies": {} - }, - "pbkdf2@3.1.2": { - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dependencies": { - "create-hash": "create-hash@1.2.0", - "create-hmac": "create-hmac@1.1.7", - "ripemd160": "ripemd160@2.0.2", - "safe-buffer": "safe-buffer@5.2.1", - "sha.js": "sha.js@2.4.11" - } - }, - "picocolors@1.0.0": { - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dependencies": {} - }, - "picomatch@2.3.1": { - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dependencies": {} - }, - "pkg-dir@5.0.0": { - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dependencies": { - "find-up": "find-up@5.0.0" - } - }, - "possible-typed-array-names@1.0.0": { - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dependencies": {} - }, - "postcss@8.4.35": { - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dependencies": { - "nanoid": "nanoid@3.3.7", - "picocolors": "picocolors@1.0.0", - "source-map-js": "source-map-js@1.0.2" - } - }, - "process-nextick-args@2.0.1": { - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dependencies": {} - }, - "process@0.11.10": { - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dependencies": {} - }, - "prompts@2.4.2": { - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dependencies": { - "kleur": "kleur@3.0.3", - "sisteransi": "sisteransi@1.0.5" - } - }, - "proxy-from-env@1.1.0": { - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dependencies": {} - }, - "public-encrypt@4.0.3": { - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dependencies": { - "bn.js": "bn.js@4.12.0", - "browserify-rsa": "browserify-rsa@4.1.0", - "create-hash": "create-hash@1.2.0", - "parse-asn1": "parse-asn1@5.1.7", - "randombytes": "randombytes@2.1.0", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "punycode@1.4.1": { - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dependencies": {} - }, - "qs@6.12.3": { - "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", - "dependencies": { - "side-channel": "side-channel@1.0.6" - } - }, - "querystring-es3@0.2.1": { - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dependencies": {} - }, - "randombytes@2.1.0": { - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "randomfill@1.0.4": { - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dependencies": { - "randombytes": "randombytes@2.1.0", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "react-dom@18.2.0_react@18.2.0": { - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "loose-envify@1.4.0", - "react": "react@18.2.0", - "scheduler": "scheduler@0.23.0" - } - }, - "react-refresh@0.14.0": { - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dependencies": {} - }, - "react@18.2.0": { - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "loose-envify@1.4.0" - } - }, - "readable-stream@2.3.8": { - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "core-util-is@1.0.3", - "inherits": "inherits@2.0.4", - "isarray": "isarray@1.0.0", - "process-nextick-args": "process-nextick-args@2.0.1", - "safe-buffer": "safe-buffer@5.1.2", - "string_decoder": "string_decoder@1.1.1", - "util-deprecate": "util-deprecate@1.0.2" - } - }, - "readable-stream@3.6.2": { - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "inherits@2.0.4", - "string_decoder": "string_decoder@1.3.0", - "util-deprecate": "util-deprecate@1.0.2" - } - }, - "resolve@1.22.8": { - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "is-core-module@2.15.0", - "path-parse": "path-parse@1.0.7", - "supports-preserve-symlinks-flag": "supports-preserve-symlinks-flag@1.0.0" - } - }, - "restore-cursor@4.0.0": { - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dependencies": { - "onetime": "onetime@5.1.2", - "signal-exit": "signal-exit@3.0.7" - } - }, - "ripemd160@2.0.2": { - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "hash-base@3.0.4", - "inherits": "inherits@2.0.4" - } - }, - "rollup@4.12.0": { - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", - "dependencies": { - "@rollup/rollup-android-arm-eabi": "@rollup/rollup-android-arm-eabi@4.12.0", - "@rollup/rollup-android-arm64": "@rollup/rollup-android-arm64@4.12.0", - "@rollup/rollup-darwin-arm64": "@rollup/rollup-darwin-arm64@4.12.0", - "@rollup/rollup-darwin-x64": "@rollup/rollup-darwin-x64@4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "@rollup/rollup-linux-arm-gnueabihf@4.12.0", - "@rollup/rollup-linux-arm64-gnu": "@rollup/rollup-linux-arm64-gnu@4.12.0", - "@rollup/rollup-linux-arm64-musl": "@rollup/rollup-linux-arm64-musl@4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "@rollup/rollup-linux-riscv64-gnu@4.12.0", - "@rollup/rollup-linux-x64-gnu": "@rollup/rollup-linux-x64-gnu@4.12.0", - "@rollup/rollup-linux-x64-musl": "@rollup/rollup-linux-x64-musl@4.12.0", - "@rollup/rollup-win32-arm64-msvc": "@rollup/rollup-win32-arm64-msvc@4.12.0", - "@rollup/rollup-win32-ia32-msvc": "@rollup/rollup-win32-ia32-msvc@4.12.0", - "@rollup/rollup-win32-x64-msvc": "@rollup/rollup-win32-x64-msvc@4.12.0", - "@types/estree": "@types/estree@1.0.5", - "fsevents": "fsevents@2.3.3" - } - }, - "safe-buffer@5.1.2": { - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dependencies": {} - }, - "safe-buffer@5.2.1": { - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dependencies": {} - }, - "scheduler@0.23.0": { - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "loose-envify@1.4.0" - } - }, - "semver@6.3.1": { - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dependencies": {} - }, - "set-function-length@1.2.2": { - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "define-data-property@1.1.4", - "es-errors": "es-errors@1.3.0", - "function-bind": "function-bind@1.1.2", - "get-intrinsic": "get-intrinsic@1.2.4", - "gopd": "gopd@1.0.1", - "has-property-descriptors": "has-property-descriptors@1.0.2" - } - }, - "setimmediate@1.0.5": { - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dependencies": {} - }, - "sha.js@2.4.11": { - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "inherits@2.0.4", - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "side-channel@1.0.6": { - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": { - "call-bind": "call-bind@1.0.7", - "es-errors": "es-errors@1.3.0", - "get-intrinsic": "get-intrinsic@1.2.4", - "object-inspect": "object-inspect@1.13.2" - } - }, - "signal-exit@3.0.7": { - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dependencies": {} - }, - "sisteransi@1.0.5": { - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dependencies": {} - }, - "source-map-js@1.0.2": { - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dependencies": {} - }, - "stdin-discarder@0.1.0": { - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dependencies": { - "bl": "bl@5.1.0" - } - }, - "stream-browserify@3.0.0": { - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dependencies": { - "inherits": "inherits@2.0.4", - "readable-stream": "readable-stream@3.6.2" - } - }, - "stream-http@3.2.0": { - "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", - "dependencies": { - "builtin-status-codes": "builtin-status-codes@3.0.0", - "inherits": "inherits@2.0.4", - "readable-stream": "readable-stream@3.6.2", - "xtend": "xtend@4.0.2" - } - }, - "string-width@6.1.0": { - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dependencies": { - "eastasianwidth": "eastasianwidth@0.2.0", - "emoji-regex": "emoji-regex@10.3.0", - "strip-ansi": "strip-ansi@7.1.0" - } - }, - "string_decoder@1.1.1": { - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "safe-buffer@5.1.2" - } - }, - "string_decoder@1.3.0": { - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "safe-buffer@5.2.1" - } - }, - "strip-ansi@7.1.0": { - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "ansi-regex@6.0.1" - } - }, - "supports-color@5.5.0": { - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "has-flag@3.0.0" - } - }, - "supports-preserve-symlinks-flag@1.0.0": { - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dependencies": {} - }, - "timers-browserify@2.0.12": { - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dependencies": { - "setimmediate": "setimmediate@1.0.5" - } - }, - "to-fast-properties@2.0.0": { - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dependencies": {} - }, - "tty-browserify@0.0.1": { - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dependencies": {} - }, - "update-browserslist-db@1.0.13_browserslist@4.23.0": { - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dependencies": { - "browserslist": "browserslist@4.23.0", - "escalade": "escalade@3.1.2", - "picocolors": "picocolors@1.0.0" - } - }, - "url@0.11.4": { - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", - "dependencies": { - "punycode": "punycode@1.4.1", - "qs": "qs@6.12.3" - } - }, - "util-deprecate@1.0.2": { - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dependencies": {} - }, - "util@0.12.5": { - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "inherits@2.0.4", - "is-arguments": "is-arguments@1.1.1", - "is-generator-function": "is-generator-function@1.0.10", - "is-typed-array": "is-typed-array@1.1.13", - "which-typed-array": "which-typed-array@1.1.15" - } - }, - "vite-plugin-https-imports@0.1.0": { - "integrity": "sha512-4RXrWmoTOkJ7vTUAFUNVvqfeznagcXdaCpnEF1D5e+rVyP6dhyf/9LNDBg2Uu2Qv1w9nPl5kkBtviqIPtEZmJQ==", - "dependencies": { - "axios": "axios@1.6.7", - "chalk": "chalk@5.3.0", - "inclusion": "inclusion@1.0.1", - "minimatch": "minimatch@9.0.3", - "ora": "ora@7.0.1" - } - }, - "vite-plugin-node-polyfills@0.22.0_vite@5.1.4": { - "integrity": "sha512-F+G3LjiGbG8QpbH9bZ//GSBr9i1InSTkaulfUHFa9jkLqVGORFBoqc2A/Yu5Mmh1kNAbiAeKeK+6aaQUf3x0JA==", - "dependencies": { - "@rollup/plugin-inject": "@rollup/plugin-inject@5.0.5", - "node-stdlib-browser": "node-stdlib-browser@1.2.0", - "vite": "vite@5.1.4" - } - }, - "vite@5.1.4": { - "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==", - "dependencies": { - "esbuild": "esbuild@0.19.12", - "fsevents": "fsevents@2.3.3", - "postcss": "postcss@8.4.35", - "rollup": "rollup@4.12.0" - } - }, - "vm-browserify@1.1.2": { - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dependencies": {} - }, - "which-typed-array@1.1.15": { - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dependencies": { - "available-typed-arrays": "available-typed-arrays@1.0.7", - "call-bind": "call-bind@1.0.7", - "for-each": "for-each@0.3.3", - "gopd": "gopd@1.0.1", - "has-tostringtag": "has-tostringtag@1.0.2" - } - }, - "xtend@4.0.2": { - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dependencies": {} - }, - "yallist@3.1.1": { - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dependencies": {} - }, - "yocto-queue@0.1.0": { - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dependencies": {} - } - } - }, - "redirects": { - "https://deno.land/x/vite_deno_plugin/mod.ts": "https://deno.land/x/vite_deno_plugin@v0.9.4/mod.ts", - "https://esm.sh/vite-plugin-https-imports": "https://esm.sh/vite-plugin-https-imports@0.1.0" - }, - "remote": { - "https://deno.land/std@0.130.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", - "https://deno.land/std@0.130.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", - "https://deno.land/std@0.130.0/testing/asserts.ts": "b0ef969032882b1f7eb1c7571e313214baa1485f7b61cf35807b2434e254365c", - "https://deno.land/std@0.135.0/collections/_comparators.ts": "bf94763e6e4f77e8dff509975eceeb7af4cbbe1cbc52df02d344bf3222fd46be", - "https://deno.land/std@0.135.0/collections/bs_node.ts": "2b3f8bab9a2a17fbae7c1916af34c737ec7db511aa71cd21f4f34004df12f308", - "https://deno.land/std@0.135.0/collections/bs_tree.ts": "d373948d3ad12a99f3f28d6046361042b574c157f8f6f53907ae9788b93b962d", - "https://deno.land/std@0.135.0/collections/rb_node.ts": "6fef24e118ee6020824850015edaab35e78566dd0e122a1dacd6c288d4d5b2f7", - "https://deno.land/std@0.135.0/collections/rb_tree.ts": "e2c7f240e291f2a776af3a781ef91f194c24028658f6ccee6c3a8b7c734b25e5", - "https://deno.land/std@0.150.0/media_types/_util.ts": "ce9b4fc4ba1c447dafab619055e20fd88236ca6bdd7834a21f98bd193c3fbfa1", - "https://deno.land/std@0.150.0/media_types/mod.ts": "2d4b6f32a087029272dc59e0a55ae3cc4d1b27b794ccf528e94b1925795b3118", - "https://deno.land/std@0.150.0/media_types/vendor/mime-db.v1.52.0.ts": "724cee25fa40f1a52d3937d6b4fbbfdd7791ff55e1b7ac08d9319d5632c7f5af", - "https://deno.land/std@0.204.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.204.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.204.0/jsonc/parse.ts": "c1096e2b7ffb4996d7ed841dfdb29a4fccc78edcc55299beaa20d6fe5facf7b6", - "https://deno.land/std@0.204.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", - "https://deno.land/std@0.204.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", - "https://deno.land/std@0.204.0/path/_common/common.ts": "9e4233b2eeb50f8b2ae10ecc2108f58583aea6fd3e8907827020282dc2b76143", - "https://deno.land/std@0.204.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.204.0/path/_common/dirname.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", - "https://deno.land/std@0.204.0/path/_common/format.ts": "11aa62e316dfbf22c126917f5e03ea5fe2ee707386555a8f513d27ad5756cf96", - "https://deno.land/std@0.204.0/path/_common/from_file_url.ts": "ef1bf3197d2efbf0297a2bdbf3a61d804b18f2bcce45548ae112313ec5be3c22", - "https://deno.land/std@0.204.0/path/_common/glob_to_reg_exp.ts": "5c3c2b79fc2294ec803d102bd9855c451c150021f452046312819fbb6d4dc156", - "https://deno.land/std@0.204.0/path/_common/is_glob.ts": "567dce5c6656bdedfc6b3ee6c0833e1e4db2b8dff6e62148e94a917f289c06ad", - "https://deno.land/std@0.204.0/path/_common/normalize.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", - "https://deno.land/std@0.204.0/path/_common/normalize_string.ts": "88c472f28ae49525f9fe82de8c8816d93442d46a30d6bb5063b07ff8a89ff589", - "https://deno.land/std@0.204.0/path/_common/relative.ts": "1af19d787a2a84b8c534cc487424fe101f614982ae4851382c978ab2216186b4", - "https://deno.land/std@0.204.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", - "https://deno.land/std@0.204.0/path/_common/to_file_url.ts": "a8cdd1633bc9175b7eebd3613266d7c0b6ae0fb0cff24120b6092ac31662f9ae", - "https://deno.land/std@0.204.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.204.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", - "https://deno.land/std@0.204.0/path/basename.ts": "04bb5ef3e86bba8a35603b8f3b69537112cdd19ce64b77f2522006da2977a5f3", - "https://deno.land/std@0.204.0/path/common.ts": "f4d061c7d0b95a65c2a1a52439edec393e906b40f1caf4604c389fae7caa80f5", - "https://deno.land/std@0.204.0/path/dirname.ts": "88a0a71c21debafc4da7a4cd44fd32e899462df458fbca152390887d41c40361", - "https://deno.land/std@0.204.0/path/extname.ts": "2da4e2490f3b48b7121d19fb4c91681a5e11bd6bd99df4f6f47d7a71bb6ecdf2", - "https://deno.land/std@0.204.0/path/format.ts": "3457530cc85d1b4bab175f9ae73998b34fd456c830d01883169af0681b8894fb", - "https://deno.land/std@0.204.0/path/from_file_url.ts": "e7fa233ea1dff9641e8d566153a24d95010110185a6f418dd2e32320926043f8", - "https://deno.land/std@0.204.0/path/glob.ts": "9c77cf47db1d786e2ebf66670824d03fd84ecc7c807cac24441eb9d5cb6a2986", - "https://deno.land/std@0.204.0/path/is_absolute.ts": "67232b41b860571c5b7537f4954c88d86ae2ba45e883ee37d3dec27b74909d13", - "https://deno.land/std@0.204.0/path/join.ts": "98d3d76c819af4a11a81d5ba2dbb319f1ce9d63fc2b615597d4bcfddd4a89a09", - "https://deno.land/std@0.204.0/path/mod.ts": "2d62a0a8b78a60e8e6f485d881bac6b61d58573b11cf585fb7c8fc50d9b20d80", - "https://deno.land/std@0.204.0/path/normalize.ts": "aa95be9a92c7bd4f9dc0ba51e942a1973e2b93d266cd74f5ca751c136d520b66", - "https://deno.land/std@0.204.0/path/parse.ts": "d87ff0deef3fb495bc0d862278ff96da5a06acf0625ca27769fc52ac0d3d6ece", - "https://deno.land/std@0.204.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", - "https://deno.land/std@0.204.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", - "https://deno.land/std@0.204.0/path/posix/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", - "https://deno.land/std@0.204.0/path/posix/dirname.ts": "f48c9c42cc670803b505478b7ef162c7cfa9d8e751b59d278b2ec59470531472", - "https://deno.land/std@0.204.0/path/posix/extname.ts": "ee7f6571a9c0a37f9218fbf510c440d1685a7c13082c348d701396cc795e0be0", - "https://deno.land/std@0.204.0/path/posix/format.ts": "b94876f77e61bfe1f147d5ccb46a920636cd3cef8be43df330f0052b03875968", - "https://deno.land/std@0.204.0/path/posix/from_file_url.ts": "b97287a83e6407ac27bdf3ab621db3fccbf1c27df0a1b1f20e1e1b5acf38a379", - "https://deno.land/std@0.204.0/path/posix/glob.ts": "86c3f06d1c98303613c74650961c3e24bdb871cde2a97c3ae7f0f6d4abbef445", - "https://deno.land/std@0.204.0/path/posix/is_absolute.ts": "159900a3422d11069d48395568217eb7fc105ceda2683d03d9b7c0f0769e01b8", - "https://deno.land/std@0.204.0/path/posix/join.ts": "0c0d84bdc344876930126640011ec1b888e6facf74153ffad9ef26813aa2a076", - "https://deno.land/std@0.204.0/path/posix/mod.ts": "6bfa8a42d85345b12dbe8571028ca2c62d460b6ef968125e498602b43b6cf6b6", - "https://deno.land/std@0.204.0/path/posix/normalize.ts": "11de90a94ab7148cc46e5a288f7d732aade1d616bc8c862f5560fa18ff987b4b", - "https://deno.land/std@0.204.0/path/posix/parse.ts": "199208f373dd93a792e9c585352bfc73a6293411bed6da6d3bc4f4ef90b04c8e", - "https://deno.land/std@0.204.0/path/posix/relative.ts": "e2f230608b0f083e6deaa06e063943e5accb3320c28aef8d87528fbb7fe6504c", - "https://deno.land/std@0.204.0/path/posix/resolve.ts": "51579d83159d5c719518c9ae50812a63959bbcb7561d79acbdb2c3682236e285", - "https://deno.land/std@0.204.0/path/posix/separator.ts": "0b6573b5f3269a3164d8edc9cefc33a02dd51003731c561008c8bb60220ebac1", - "https://deno.land/std@0.204.0/path/posix/to_file_url.ts": "08d43ea839ee75e9b8b1538376cfe95911070a655cd312bc9a00f88ef14967b6", - "https://deno.land/std@0.204.0/path/posix/to_namespaced_path.ts": "c9228a0e74fd37e76622cd7b142b8416663a9b87db643302fa0926b5a5c83bdc", - "https://deno.land/std@0.204.0/path/relative.ts": "23d45ede8b7ac464a8299663a43488aad6b561414e7cbbe4790775590db6349c", - "https://deno.land/std@0.204.0/path/resolve.ts": "5b184efc87155a0af9fa305ff68a109e28de9aee81fc3e77cd01380f19daf867", - "https://deno.land/std@0.204.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", - "https://deno.land/std@0.204.0/path/to_file_url.ts": "edaafa089e0bce386e1b2d47afe7c72e379ff93b28a5829a5885e4b6c626d864", - "https://deno.land/std@0.204.0/path/to_namespaced_path.ts": "cf8734848aac3c7527d1689d2adf82132b1618eff3cc523a775068847416b22a", - "https://deno.land/std@0.204.0/path/windows/_util.ts": "f32b9444554c8863b9b4814025c700492a2b57ff2369d015360970a1b1099d54", - "https://deno.land/std@0.204.0/path/windows/basename.ts": "8a9dbf7353d50afbc5b221af36c02a72c2d1b2b5b9f7c65bf6a5a2a0baf88ad3", - "https://deno.land/std@0.204.0/path/windows/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", - "https://deno.land/std@0.204.0/path/windows/dirname.ts": "5c2aa541384bf0bd9aca821275d2a8690e8238fa846198ef5c7515ce31a01a94", - "https://deno.land/std@0.204.0/path/windows/extname.ts": "07f4fa1b40d06a827446b3e3bcc8d619c5546b079b8ed0c77040bbef716c7614", - "https://deno.land/std@0.204.0/path/windows/format.ts": "343019130d78f172a5c49fdc7e64686a7faf41553268961e7b6c92a6d6548edf", - "https://deno.land/std@0.204.0/path/windows/from_file_url.ts": "d53335c12b0725893d768be3ac6bf0112cc5b639d2deb0171b35988493b46199", - "https://deno.land/std@0.204.0/path/windows/glob.ts": "0286fb89ecd21db5cbf3b6c79e2b87c889b03f1311e66fb769e6b905d4142332", - "https://deno.land/std@0.204.0/path/windows/is_absolute.ts": "245b56b5f355ede8664bd7f080c910a97e2169972d23075554ae14d73722c53c", - "https://deno.land/std@0.204.0/path/windows/join.ts": "e6600bf88edeeef4e2276e155b8de1d5dec0435fd526ba2dc4d37986b2882f16", - "https://deno.land/std@0.204.0/path/windows/mod.ts": "c3d1a36fbf9f6db1320bcb4fbda8de011d25461be3497105e15cbea1e3726198", - "https://deno.land/std@0.204.0/path/windows/normalize.ts": "9deebbf40c81ef540b7b945d4ccd7a6a2c5a5992f791e6d3377043031e164e69", - "https://deno.land/std@0.204.0/path/windows/parse.ts": "120faf778fe1f22056f33ded069b68e12447668fcfa19540c0129561428d3ae5", - "https://deno.land/std@0.204.0/path/windows/relative.ts": "026855cd2c36c8f28f1df3c6fbd8f2449a2aa21f48797a74700c5d872b86d649", - "https://deno.land/std@0.204.0/path/windows/resolve.ts": "5ff441ab18a2346abadf778121128ee71bda4d0898513d4639a6ca04edca366b", - "https://deno.land/std@0.204.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", - "https://deno.land/std@0.204.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", - "https://deno.land/std@0.204.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", - "https://deno.land/std@v0.204.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", - "https://deno.land/std@v0.204.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", - "https://deno.land/std@v0.204.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", - "https://deno.land/std@v0.204.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@v0.204.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", - "https://deno.land/std@v0.204.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", - "https://deno.land/std@v0.204.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", - "https://deno.land/std@v0.204.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", - "https://deno.land/std@v0.204.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", - "https://deno.land/std@v0.204.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", - "https://deno.land/std@v0.204.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", - "https://deno.land/std@v0.204.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", - "https://deno.land/std@v0.204.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", - "https://deno.land/std@v0.204.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", - "https://deno.land/std@v0.204.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", - "https://deno.land/std@v0.204.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", - "https://deno.land/std@v0.204.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", - "https://deno.land/std@v0.204.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", - "https://deno.land/std@v0.204.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", - "https://deno.land/std@v0.204.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", - "https://deno.land/std@v0.204.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", - "https://deno.land/std@v0.204.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", - "https://deno.land/std@v0.204.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", - "https://deno.land/std@v0.204.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", - "https://deno.land/std@v0.204.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", - "https://deno.land/std@v0.204.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@v0.204.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", - "https://deno.land/std@v0.204.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", - "https://deno.land/std@v0.204.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", - "https://deno.land/std@v0.204.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", - "https://deno.land/std@v0.204.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", - "https://deno.land/std@v0.204.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", - "https://deno.land/std@v0.204.0/testing/asserts.ts": "b4e4b1359393aeff09e853e27901a982c685cb630df30426ed75496961931946", - "https://deno.land/x/mixins@0.7.4/apply.ts": "dad7095324f5ce23693a0bc0eb3238f230c0ed2160ea8c285f3773ff7c76dcb0", - "https://deno.land/x/mock@0.15.2/asserts.ts": "54913a48cf2dc611390b452e7669869eeeff699de49449ebc6ae57d7b48b4f67", - "https://deno.land/x/mock@0.15.2/callbacks.ts": "cb392fab2224887e1838495c729491c19e660b716c59b646615c12c1c9c9617a", - "https://deno.land/x/mock@0.15.2/deps.ts": "0849c993e0777c6e6f35fe0da1c3a30cfaaf74da18cacad116357e6f8294c25d", - "https://deno.land/x/mock@0.15.2/mock.ts": "ab95ccfc252e97670aab687a8b6bcf11ee595174ab3dc7c3f1cdeaf6e1ed2fdc", - "https://deno.land/x/mock@0.15.2/mod.ts": "d032317cb9ac497ba28355a07c1cd389d992f140a9fccca5224a3daf19ef2210", - "https://deno.land/x/mock@0.15.2/time.ts": "0f2948cf4ab62f02e8133a37d88b425d2d7f848d639870bbe815559971190445", - "https://deno.land/x/mock@0.15.2/time_deps.ts": "3fca1b385d8661712b66780839971c4f7b70cc8f7d33e209c92ac684a1ca1edf", - "https://deno.land/x/valibot@v0.19.0/mod.ts": "62e8bad0367465ff9baf1861ba23e51e2fe19feaea2fe298965f1b4d46a30ac8", - "https://deno.land/x/valibot@v0.19.0/src/error/ValiError/ValiError.ts": "e38d4f518a6071c5e1effb561944f58a8d3132c64984be2d3e9408eb094c0959", - "https://deno.land/x/valibot@v0.19.0/src/error/ValiError/index.ts": "cf20d1fb4425bf9824c0b8e6c42743409ac9b79dd71f92b9fdbf8168ce303594", - "https://deno.land/x/valibot@v0.19.0/src/error/flatten/flatten.ts": "c38878bdcbe3b69267026469c41426c297222ca3e97cae575c37b7b5ee5f7207", - "https://deno.land/x/valibot@v0.19.0/src/error/flatten/index.ts": "253b6a678058122873a54cbbf6bd6599d9538bec3586339cff242a2fa11124b3", - "https://deno.land/x/valibot@v0.19.0/src/error/index.ts": "582ea7635c2b29035e77909ba66184756ccadfe801441fe856d301abf3f016e2", - "https://deno.land/x/valibot@v0.19.0/src/index.ts": "f5800fee7d93c6011eaa69ceb4abc40ec4078a674f61b09024a5188eafe95dfe", - "https://deno.land/x/valibot@v0.19.0/src/methods/brand/brand.ts": "186a8b0565ff4005156345745c28c9366a772521f8dd707ca6ce468cc9a63737", - "https://deno.land/x/valibot@v0.19.0/src/methods/brand/index.ts": "710f22d6676f01869005640bda3cdee264b00820ce6cbcdca9df1290f6b9adeb", - "https://deno.land/x/valibot@v0.19.0/src/methods/coerce/coerce.ts": "96c3580990784f24ca84262a9c1d1851e7ad176a83c4c9f68fece6c3259fd787", - "https://deno.land/x/valibot@v0.19.0/src/methods/coerce/coerceAsync.ts": "de6c1714f1788c45fde332168375a237502d98153341a6187f2660c4afdad27c", - "https://deno.land/x/valibot@v0.19.0/src/methods/coerce/index.ts": "ee64fe8c55b0d2ae738013ada9db92178a245bb67e68d1fc906e45a284485362", - "https://deno.land/x/valibot@v0.19.0/src/methods/fallback/fallback.ts": "364966e617591e0cc8ce0ce8e5382f5891137777a9ac1984d6554292d42afcea", - "https://deno.land/x/valibot@v0.19.0/src/methods/fallback/fallbackAsync.ts": "703fae7b6b7e49d40cc52022441e0a7646229e2b0621c93e973f3fcbed925c8c", - "https://deno.land/x/valibot@v0.19.0/src/methods/fallback/index.ts": "44a9494e09eb84f3a5be700f85a7559f4a3b2552d37c05a6f821cc9621b97834", - "https://deno.land/x/valibot@v0.19.0/src/methods/getDefault/getDefault.ts": "e1f61c179a0c789efb7e775c9716809769211416deb5baad54e42446c9629c6d", - "https://deno.land/x/valibot@v0.19.0/src/methods/getDefault/index.ts": "a237ed8714b898bc31d856ea5c1bd46f5a3337a60964f1198656ac58a30095b4", - "https://deno.land/x/valibot@v0.19.0/src/methods/index.ts": "1ee664311e2de117f1c1fd010b04d2247defaffd451da11d86c69685006ffd00", - "https://deno.land/x/valibot@v0.19.0/src/methods/is/index.ts": "327fc84a5ed4eff4af039753173f4f6ad8cc769b95fabbd01dce9a1fe3481c54", - "https://deno.land/x/valibot@v0.19.0/src/methods/is/is.ts": "974aff2a2af6ae5c986648bbc71411a18e9de81daf6dfaa0e83d7adb792bd096", - "https://deno.land/x/valibot@v0.19.0/src/methods/keyof/index.ts": "7d50e6f637ee4a19365e3044b83ff1821bab4367271c057781e396324b93cf3c", - "https://deno.land/x/valibot@v0.19.0/src/methods/keyof/keyof.ts": "faa41832458a8d9ab7b780de44b19b3f80afe9608dad2863151f3fac75fa7e97", - "https://deno.land/x/valibot@v0.19.0/src/methods/merge/index.ts": "ca839a5e99996869dc9bc9522a4791d342dd1071d4c7ef5fda42a7a3b1ddbf23", - "https://deno.land/x/valibot@v0.19.0/src/methods/merge/merge.ts": "2af8022a09e74f9cf2ab1123d388d792e824818442e6efba551518ed8e3e0ed1", - "https://deno.land/x/valibot@v0.19.0/src/methods/merge/mergeAsync.ts": "447deeeaa801d7c66ed89ea788af0604c813d63d7f6659e079e77298694cf61b", - "https://deno.land/x/valibot@v0.19.0/src/methods/omit/index.ts": "85e672fea9a1146e9efd099bc5fac0e9617c7d941eb3f1afce5160c95c6d8561", - "https://deno.land/x/valibot@v0.19.0/src/methods/omit/omit.ts": "4b66dde4031389671851c87a9d11af5fc756037bdb076f2e2b8adfffb087176a", - "https://deno.land/x/valibot@v0.19.0/src/methods/omit/omitAsync.ts": "36f4f05676dac5ccddcb896c605498ed9404b5c6b14beefba4ea427ed29dbda0", - "https://deno.land/x/valibot@v0.19.0/src/methods/parse/index.ts": "669ec1a3e7e825f7344f78162e9396fa18106ebc3cb5118a2d97077a3a78c037", - "https://deno.land/x/valibot@v0.19.0/src/methods/parse/parse.ts": "21ce067a7e6113ba69ec366e9ba9921cfa93d4a6d2e3f5394202d41e1585e717", - "https://deno.land/x/valibot@v0.19.0/src/methods/parse/parseAsync.ts": "314758ff9a3f046877f1defc3f1c1e316d8d84bf146fa017c18825f76de17608", - "https://deno.land/x/valibot@v0.19.0/src/methods/partial/index.ts": "2c9fbdbb46230dfb88433be8bcb32bbce703856d73dd80e520c489fda18dea8e", - "https://deno.land/x/valibot@v0.19.0/src/methods/partial/partial.ts": "b9bbd909cd5d7928fb5788ae5f8b6da3bab9c8b5c07b5afcfce2e07bca6e0859", - "https://deno.land/x/valibot@v0.19.0/src/methods/partial/partialAsync.ts": "c4e8c5b799fec5f6eb139ab83376b54736b9b825ae327e901c673776beee86c7", - "https://deno.land/x/valibot@v0.19.0/src/methods/passthrough/index.ts": "0c70d832a9da0d3ee9b219d5583bc29a663b66ebfd258ebc608fe8556fe956a9", - "https://deno.land/x/valibot@v0.19.0/src/methods/passthrough/passthrough.ts": "7dca87b522df0a4a6e349072d1ed95068de7659e7f70b7e987c815c402c3d961", - "https://deno.land/x/valibot@v0.19.0/src/methods/passthrough/passthroughAsync.ts": "1483875a6d8a551d1732450563178b33a9a1bbece4f1badac9381f64451ab7b2", - "https://deno.land/x/valibot@v0.19.0/src/methods/pick/index.ts": "29b89f8597ddc130e8b8ce9ddacddd95e3deeea492c2f6a3f7a6ee17318bebbe", - "https://deno.land/x/valibot@v0.19.0/src/methods/pick/pick.ts": "0b9a5da032f0055b852c34f4c7b6bcf2fdbc8333324e6ca36b73011550bd4db0", - "https://deno.land/x/valibot@v0.19.0/src/methods/pick/pickAsync.ts": "0e9ab244cb4f8eb3ac8fc123f65d782d0c23a29439d574feb4924ceec7007849", - "https://deno.land/x/valibot@v0.19.0/src/methods/required/index.ts": "9bdcaedd3f4e8cf66620c77a427dfbae8bcd2c393dc5cac460287cea8ab4c59f", - "https://deno.land/x/valibot@v0.19.0/src/methods/required/required.ts": "92a2823374e133d75a631571205ee7200e587df9d534ced021b26ebb89adb352", - "https://deno.land/x/valibot@v0.19.0/src/methods/required/requiredAsync.ts": "6be118ccfa5acca69fff2bc5f8fc920ae9ea52107226654d31c26fc453ae9079", - "https://deno.land/x/valibot@v0.19.0/src/methods/safeParse/index.ts": "47442f36db5a8cb85e67790a9d8590613853e528309e8d40fa2b58ae173e978a", - "https://deno.land/x/valibot@v0.19.0/src/methods/safeParse/safeParse.ts": "77f6f841c310110cde4c5c685a080b1dec170a99a2810712f218eb64c12116e9", - "https://deno.land/x/valibot@v0.19.0/src/methods/safeParse/safeParseAsync.ts": "0fb03c79252bdc05cf40821f3115b25a57dbe7523fa0ff308094feb56433af65", - "https://deno.land/x/valibot@v0.19.0/src/methods/safeParse/types.ts": "267173db70ed1259e6ae3a62385be645277d2d29aca8badd7817254bd4da36b3", - "https://deno.land/x/valibot@v0.19.0/src/methods/strict/index.ts": "df5ca04019a43710000edcecf48dc8ce4ad469785a5e56c5536c9d0f0498ab39", - "https://deno.land/x/valibot@v0.19.0/src/methods/strict/strict.ts": "d48a7c5e1a4a8f3608bc785351162bdd5ea15e295d585e19ca88e77482224f8f", - "https://deno.land/x/valibot@v0.19.0/src/methods/strict/strictAsync.ts": "098b6fa75748ecdc8737578028801e68f5315d6e341e6c3059a3910377836974", - "https://deno.land/x/valibot@v0.19.0/src/methods/strip/index.ts": "e1f7be7363258c95e616538f9a31b6c878cd6619941cd9b06c7948d6e8a4977d", - "https://deno.land/x/valibot@v0.19.0/src/methods/strip/strip.ts": "dc0fbc656489cdc40137300b0593de3be4da868a174a68a8834222621d46214e", - "https://deno.land/x/valibot@v0.19.0/src/methods/strip/stripAsync.ts": "0ee9723c7beb5f5631007820dea761df76f1e1caf3474e142123d76eb27bee7a", - "https://deno.land/x/valibot@v0.19.0/src/methods/transform/index.ts": "d70b89ce2088b9cf07b371d7b03a5634f6fb92411a636d06addafc5b26c712a0", - "https://deno.land/x/valibot@v0.19.0/src/methods/transform/transform.ts": "64c285872fe7ed15f3459ecf6dff7384ef16b241e201b33b8fcb86e802daf8b2", - "https://deno.land/x/valibot@v0.19.0/src/methods/transform/transformAsync.ts": "1d0ac0b9abcff15e4ada21026ed4bbbda77df2e75ba5980d3a5e7b3863e2a7db", - "https://deno.land/x/valibot@v0.19.0/src/methods/unwrap/index.ts": "c650d5956fec65bb504a21ffb825c0b166ab8600acbf4b245b529000c2603458", - "https://deno.land/x/valibot@v0.19.0/src/methods/unwrap/unwrap.ts": "3e5bf6716e55326266d3e1c45f0a86665624b06b1a8e5c130be189c66105112e", - "https://deno.land/x/valibot@v0.19.0/src/methods/withDefault/index.ts": "e66c01db567dec0a880f13b9a757a1a2fbe829fe7ebd95abfba2590af03e18e4", - "https://deno.land/x/valibot@v0.19.0/src/methods/withDefault/withDefault.ts": "2bd1b857b5a8bf50c231e88e68040ddbbd46a01ec4060eb2b2abfc3cc8934c3c", - "https://deno.land/x/valibot@v0.19.0/src/schemas/any/any.ts": "948993ffeed4d5d25fd40bd625c314781ccb69c91aee1f9310d9487e4254afef", - "https://deno.land/x/valibot@v0.19.0/src/schemas/any/anyAsync.ts": "cfcc2b7dbe38cf25015a81a1172bfbd74a4ba16bebabac6ad0e5b779779ecd04", - "https://deno.land/x/valibot@v0.19.0/src/schemas/any/index.ts": "de190728871b0117983dae84695568b0914e3f1118e9491aeee9f2b443db84ad", - "https://deno.land/x/valibot@v0.19.0/src/schemas/array/array.ts": "6acc69f968c397e86810e1124b2456d97b68a19c45501c07ec67e0da543b3a67", - "https://deno.land/x/valibot@v0.19.0/src/schemas/array/arrayAsync.ts": "180e22c8ff22870120be86d8aa8a4b231f9755f91992d18e636b1145c1dc7b99", - "https://deno.land/x/valibot@v0.19.0/src/schemas/array/index.ts": "deb0be133a6d9a4efdb6b2333ad9195a2de12f883abfb2c88643c74b816d52dc", - "https://deno.land/x/valibot@v0.19.0/src/schemas/array/types.ts": "b8d861ed1f3801266a245d6277342dd30e9c4b3e6f982fcad64c631e02b17d99", - "https://deno.land/x/valibot@v0.19.0/src/schemas/bigint/bigint.ts": "dfb35efed865704181eadf95a790f24f3fd3d50750eeaf8fc5c46a584f8c4ceb", - "https://deno.land/x/valibot@v0.19.0/src/schemas/bigint/bigintAsync.ts": "bcdfec7282bcf68dd2bf11e97e1ec8fbe6e636b2f9ada6241b09c51fc78f6d5b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/bigint/index.ts": "63947da2d953783500de43444755b403c54a9ab3419684d972f744090cb8d9a5", - "https://deno.land/x/valibot@v0.19.0/src/schemas/blob/blob.ts": "d183c6be08f29e72cc8999caaaf84a906d6f793c405d3d7c559803db94d780f2", - "https://deno.land/x/valibot@v0.19.0/src/schemas/blob/blobAsync.ts": "0f7440f417a2bb11bce5807257bb365ee0a1acc7fbaa0c3cd7a815424754f65a", - "https://deno.land/x/valibot@v0.19.0/src/schemas/blob/index.ts": "bbee52cbc45e9863d70701ee816a653e43bb252f38f0cd56390cb0f15e2d96ed", - "https://deno.land/x/valibot@v0.19.0/src/schemas/boolean/boolean.ts": "2f73dadbff44103bd833e421257b6f97e906008459c8ae92286d69a7a8912209", - "https://deno.land/x/valibot@v0.19.0/src/schemas/boolean/booleanAsync.ts": "ad0a6aef076175406f4ef28a9cc55d8ba056d67eda0f297b20173b4c294d63e8", - "https://deno.land/x/valibot@v0.19.0/src/schemas/boolean/index.ts": "55b310528887b6f55ad1dce9efb2825a23ccb7688cf73ffa16582924da6cdaa2", - "https://deno.land/x/valibot@v0.19.0/src/schemas/date/date.ts": "58991842fa3f25f02fe1f4ad7598f90e20c6e1e7ab4981eb689ade835ff2aabe", - "https://deno.land/x/valibot@v0.19.0/src/schemas/date/dateAsync.ts": "9224411ee55492ffe514eb3d9c3c6ec644013dc65c538522477e48f301875f9e", - "https://deno.land/x/valibot@v0.19.0/src/schemas/date/index.ts": "054c71ead21736a8806abaee377e190e9258db90c218b63f05e0619074456a57", - "https://deno.land/x/valibot@v0.19.0/src/schemas/enumType/enumType.ts": "004f9a31116f41bb4a8567a0ad94e76e76f1b6681e36513f3f255be5487044b6", - "https://deno.land/x/valibot@v0.19.0/src/schemas/enumType/enumTypeAsync.ts": "a0e8df72eb82cf0cb7ef0e8260e218f9318baaa7cc082603af0d24de29895571", - "https://deno.land/x/valibot@v0.19.0/src/schemas/enumType/index.ts": "293af35dcd87cbcdfd1b36a4507b6afb2bb508aaca9432eb98fcf0cede0e1eac", - "https://deno.land/x/valibot@v0.19.0/src/schemas/enumType/types.ts": "8d1b0ef41a6ab9a05c569431e714bff660386f84c1f080c6aa503170566bcaf5", - "https://deno.land/x/valibot@v0.19.0/src/schemas/index.ts": "b72ffb141387d5959c9e38ccc524cae57f9a666a9d8fe61881490571f4100b38", - "https://deno.land/x/valibot@v0.19.0/src/schemas/instance/index.ts": "527dde08b093322fc5a09488d3cb883b1d1bb25e01e2f8a7f3eabab8e2a4cc8e", - "https://deno.land/x/valibot@v0.19.0/src/schemas/instance/instance.ts": "11b9f57248ccb66dd96d41f7cab1b721464e95f46858d381ebf7e5d7387f89d1", - "https://deno.land/x/valibot@v0.19.0/src/schemas/instance/instanceAsync.ts": "5399f65d3e1546d5f5e0ccf4ebf329e5d1ae289c51d88e50980485151580fbc4", - "https://deno.land/x/valibot@v0.19.0/src/schemas/intersection/index.ts": "730cbd468166fec1f61b2daf87aa0b65d3b24d0928b77a5f57193341ec43d2da", - "https://deno.land/x/valibot@v0.19.0/src/schemas/intersection/intersection.ts": "2d869bd2785818cec031992877a390dbec5e8f00f378115a0dc0d0756c44e06a", - "https://deno.land/x/valibot@v0.19.0/src/schemas/intersection/intersectionAsync.ts": "c36b1f65190f2af304c3158fad1540bc62df1b8dd305a588c87d3bca3fc0baed", - "https://deno.land/x/valibot@v0.19.0/src/schemas/intersection/utils/index.ts": "d94d638412275f499c9cc79b0bee16c46f56a6c6301cdf1de51622dc1d640b32", - "https://deno.land/x/valibot@v0.19.0/src/schemas/intersection/utils/mergeOutputs/index.ts": "168a4cca90c8817b7d51c129679dda65e6cd6fb2543b0d3e518f983ffea229c2", - "https://deno.land/x/valibot@v0.19.0/src/schemas/intersection/utils/mergeOutputs/mergeOutputs.ts": "689764404846daef458ea8caa3cbd23fed780beac3aa0672588df6ee46a0a698", - "https://deno.land/x/valibot@v0.19.0/src/schemas/literal/index.ts": "aefd35b7f0a34f2108449fd19e5ac6439b88cd07de0367aff657606ee17d7c15", - "https://deno.land/x/valibot@v0.19.0/src/schemas/literal/literal.ts": "19b14bb74eaced5128f29b90f5b9d031c71895ea3a5d51a1fb63ebbd60a796e6", - "https://deno.land/x/valibot@v0.19.0/src/schemas/literal/literalAsync.ts": "ad4a7e36631348458168e3e3aead9c0659fbd8fbfb7ea7bd0a3ca173acdd21c3", - "https://deno.land/x/valibot@v0.19.0/src/schemas/literal/types.ts": "89e8edc96dd049ec0d055ab23f7e5454fff6d5deb6514c69a5cb0c1ce44f66e9", - "https://deno.land/x/valibot@v0.19.0/src/schemas/map/index.ts": "39056f98686cc72b3de9a4c590bf690ec6282d781795ce72fd1cb4ab14bfee87", - "https://deno.land/x/valibot@v0.19.0/src/schemas/map/map.ts": "da866b7bc7b844185a9895ea09c111e3b50a9735ec084412f52e17abc65c5ec0", - "https://deno.land/x/valibot@v0.19.0/src/schemas/map/mapAsync.ts": "5fa8d2f37d999b33699019727e30ece67d4767a7231c40cdc647f41c9e1b2362", - "https://deno.land/x/valibot@v0.19.0/src/schemas/map/types.ts": "c7437b7cc51e263d21d94de4e35f5c664706f38dfc087401711c6d564ea0e209", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nan/index.ts": "24dd3ab7f2cfbcafb941b5334e40f8a3bdc6f7ff6e426056af5d0dc32447da5b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nan/nan.ts": "cfc28573957a1abf4b54cfb8eb898bd7847687b07eb672bc4c5c73dfd3c3c26b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nan/nanAsync.ts": "9a05169792508b464847f424a0c824c9cb701f3e7d578486fe59cce5e892b87a", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nativeEnum/index.ts": "3077b9605c597f138ee1f822b95b070ae2d02f0106e776da89d308766d0af56b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nativeEnum/nativeEnum.ts": "55b3127cc40ca19ee260396fbbe1632306eb725a5ba106bd2b8cf43dd2240ab8", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nativeEnum/nativeEnumAsync.ts": "b3d7de0a6752acbf412cd43c8d5f571f5c7c7dc4a3604e048b1eedc550442132", - "https://deno.land/x/valibot@v0.19.0/src/schemas/never/index.ts": "15342661525254629f49bf24a5170373539604280d70fa678911f22803280bc2", - "https://deno.land/x/valibot@v0.19.0/src/schemas/never/never.ts": "3535d860acb6c202b98db260f98617a0d7ca8d7b8da983d8ae944aca17fc7411", - "https://deno.land/x/valibot@v0.19.0/src/schemas/never/neverAsync.ts": "a6a6a17988789cee1aed9678a35cf462d27e3b83ae77b0430f26fb1e22ea3cfb", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonNullable/index.ts": "a7325d23d2689a72dbbb2521fd012259ce23dfff4d09a1c4e616b4eda4f92d1b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonNullable/nonNullable.ts": "ba28c790653d657d2e352e42a62b184059333135a76bc0289f2d9461ddc56a95", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonNullable/nonNullableAsync.ts": "fffdfd77e32f0c2548a01f1b478d59c28eb2aa6f3bb66f26d632d45a095e4a1b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonNullish/index.ts": "74cb026803df4042c74913e2256c500f0ededf1c939654504b985799e5bea1c2", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonNullish/nonNullish.ts": "7c85a85dbd4f584dd3ba1451f9c3850f3cefa11c52a3dbc40a9e0348fbfe7dfa", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonNullish/nonNullishAsync.ts": "4814972de7278aa29adad4fd4e0b5b6347425a4b3aa74111fa7825be5dc31597", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonOptional/index.ts": "537b4db27c7c6f64a8e1f83684eebec45d6a9acea98118dfa2c51eccd5ae3be2", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonOptional/nonOptional.ts": "20e8e86ca56f642a8fc9c2b35ec0c0b7285085c0c5322d6bdab02e0355bc27a5", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nonOptional/nonOptionalAsync.ts": "31e1f3e7c81e1d1d1ace907a44e2c2d672ed06f21bca2c79333a8728bf472870", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullType/index.ts": "da90e26dbe4c3b706166798d244ffc2bcfbbf3f944922108f481ed56412202a9", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullType/nullType.ts": "663bd20721a4df89633a2b2e38d0006335023b93f300e7b11b90ccfd334ec4ca", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullType/nullTypeAsync.ts": "91bc2ed3829dfbcfa62c291ba68d5eea462c44a59356c7ec7576518777ec4e55", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullable/index.ts": "aef898ad0cedabb408eb1476843548a4de42139c9704101fe11c0c3068271a4e", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullable/nullable.ts": "6b01f2002236185daa6f4f7043ff3a8d8b222360580dbce2544ac7c6d3b1b19f", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullable/nullableAsync.ts": "5ec5f3ed869b13088db81ef024c5e1aec2c1283c7e907ce41180c2a5286e979d", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullish/index.ts": "98735c824ddfe5d96bd802db3937b5c5c47c87470a4766ce60cf4c381c3b0327", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullish/nullish.ts": "aba7f491b49cdbfde16e52fba5c87f9b74bc56c0f1aa67baee5e92a2ecbc1432", - "https://deno.land/x/valibot@v0.19.0/src/schemas/nullish/nullishAsync.ts": "df13116f22c5f7ccf16aa9efe8d305d16d8451c56940e8ad636ee72ad3de9410", - "https://deno.land/x/valibot@v0.19.0/src/schemas/number/index.ts": "21e61a3f12f35f4ad5b1c6691624502218a4df864dbebdcafbbd3d027fb4615d", - "https://deno.land/x/valibot@v0.19.0/src/schemas/number/number.ts": "572cf04b4a687c0183712e130f9631124b93535c5332611e08359ca6712d758d", - "https://deno.land/x/valibot@v0.19.0/src/schemas/number/numberAsync.ts": "5765a31fad67fe0ed8e5ac35a3f8c28aab736885bada1bc514e744ededa32020", - "https://deno.land/x/valibot@v0.19.0/src/schemas/object/index.ts": "6cd2f39fa479fe547d7bcaa5dadf876c9b2a383e7f7394c0e32e3ca085d3d27d", - "https://deno.land/x/valibot@v0.19.0/src/schemas/object/object.ts": "3e58527a2dd986a29d300ddf5335b58f66fa40e10971096152652439fde692c8", - "https://deno.land/x/valibot@v0.19.0/src/schemas/object/objectAsync.ts": "4c50f842e3df8312c74e41cae197279acff54bb5d125733f56f8138411cc2d62", - "https://deno.land/x/valibot@v0.19.0/src/schemas/object/types.ts": "bb508e79fc2f9ed5f6c05099c2cfbb5ced5cdce513021341394bbfc17ab662cc", - "https://deno.land/x/valibot@v0.19.0/src/schemas/optional/index.ts": "d9783508e7c2937c07f7b18c48c9184f4ab92958f8145adb897cb5aef5235a4d", - "https://deno.land/x/valibot@v0.19.0/src/schemas/optional/optional.ts": "fdad250e57ea61c88e7bde08ec1a66c589da0f11cb92a0d4704c114d4c93fa68", - "https://deno.land/x/valibot@v0.19.0/src/schemas/optional/optionalAsync.ts": "db41b7a32a6907c3071b9a36fdc61b26f349e090a915f24b92f7f36464a07b49", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/index.ts": "27ca5007ddfe12cf771c441692d0592ca9dcea0f99ba1b7da016b6fa24899a9c", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/record.ts": "dab6adef01e5fee95d828bc17a8faf88c10b6a6c7c6e3973d48804c03e2b4530", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/recordAsync.ts": "7d52a375d9bfd6c1c2eeb5ed30d8cdd56b53bbd62f0893be917cb9bdfd3684d9", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/types.ts": "cc3e566c3f7d5b2b38ed921d931f483fc03ab13bd8e5ba5490787355f60caf0f", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/utils/getRecordArgs/getRecordArgs.ts": "983747867d8773a81b9e7e9527150c58de2cc88bdaf7dac8fb016c13c72768f3", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/utils/getRecordArgs/index.ts": "c6984f5521bca4f657b8ba05efd3a928c91b145a7457af9882af94ba73193dff", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/utils/index.ts": "fe32df7fecc9f6ebf01f3b2a678658859e413f64e196a82814ccc89bff2790e5", - "https://deno.land/x/valibot@v0.19.0/src/schemas/record/values.ts": "85dcca30f64839cefd3af98781b62ee7f3b6e66a9ce2a4fdb7558fdab3f4a98a", - "https://deno.land/x/valibot@v0.19.0/src/schemas/recursive/index.ts": "0892fe69cb7bd02fcfd29711795c2aa569644e429d69bbef1bd8cfd8e4c1c9bc", - "https://deno.land/x/valibot@v0.19.0/src/schemas/recursive/recursive.ts": "d8a2ca27eb450054b560d65e91196f63fac7c0e61b7a321d171aa656a47cebf8", - "https://deno.land/x/valibot@v0.19.0/src/schemas/recursive/recursiveAsync.ts": "05989193c92e8538885d9c57a14400bfeeb2fec3c35ea3614810388070aa07ed", - "https://deno.land/x/valibot@v0.19.0/src/schemas/set/index.ts": "8545f57266673224be3d065ed39776cc66c2717f409c8bd982632c672e98a261", - "https://deno.land/x/valibot@v0.19.0/src/schemas/set/set.ts": "b3dc87815b328331bdcc195010f0f121ce870d84551f18c1c5dbe784b918a300", - "https://deno.land/x/valibot@v0.19.0/src/schemas/set/setAsync.ts": "49ffd2797db2bea8a7c6c0b88fcf3e82b28092928856d764fc676b3faa198e80", - "https://deno.land/x/valibot@v0.19.0/src/schemas/set/types.ts": "4167bfaf1426c2d4bf0923fa71d4cdad051bde500935e1f7c27a0c8fdad3bfe8", - "https://deno.land/x/valibot@v0.19.0/src/schemas/special/index.ts": "e860bc91b74ca091d8ff7241b22fe63367a166fda8b4b9d16c1d9c762d0f093c", - "https://deno.land/x/valibot@v0.19.0/src/schemas/special/special.ts": "8c04a2e753d29527a3eb5f47b84797789657f6385317f96e5eab3cd43ca0b654", - "https://deno.land/x/valibot@v0.19.0/src/schemas/special/specialAsync.ts": "4a59a0842bcf2dae6d2504dfb8ef0caa4d796e42d67a03a03cec6a4ba650e787", - "https://deno.land/x/valibot@v0.19.0/src/schemas/string/index.ts": "ab64782b4c251728ac5ee210edc4cc2f10c9bf4e83b4db93958148563ba37417", - "https://deno.land/x/valibot@v0.19.0/src/schemas/string/string.ts": "f6257e90e7d02e482a48618cf1f053491de23bf705cb53807de3177cc9533c87", - "https://deno.land/x/valibot@v0.19.0/src/schemas/string/stringAsync.ts": "4dcaee03fd2a1c1ceb997c5b0cb52b8baae59a8eb20945eb997cdc64d9b43bba", - "https://deno.land/x/valibot@v0.19.0/src/schemas/symbol/index.ts": "2af72055bc535190e3105623399ca989e38cebb3008b494014d38480b9da17ba", - "https://deno.land/x/valibot@v0.19.0/src/schemas/symbol/symbol.ts": "7686d74e1e4d8f2cfba29d023b55b53a507620d2ddede6b4110d0d8385aba88b", - "https://deno.land/x/valibot@v0.19.0/src/schemas/symbol/symbolAsync.ts": "55140a0e810016a5590a25d2fff1fd50e32a3526a4905e64358b516da2a3c84d", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/index.ts": "99e8340659655b7c63192256a6dea75a72d2677c7be4d716a35ba43f1778b3ce", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/tuple.ts": "db1301c2c313e2261846cb1927f583c18f1421b04069086121d615ba3b3627c5", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/tupleAsync.ts": "c3a64b050bba25074b3aa1194c95446c66f1c2e360e2a0576830f0cd2a96419f", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/types.ts": "cf6013039d453b799fa7a88ee475a521df87aeb8abb8d806c108ce6af1dbaac5", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/utils/getTupleArgs/getTupleArgs.ts": "cf3f67fb54b3a1fb1273757e66fe41644045825d2b8b8009f0b9ba664c993e71", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/utils/getTupleArgs/index.ts": "753a64c08f68dd04d9a5322fac47b7e96845779b53f5f8a66318c89599fab464", - "https://deno.land/x/valibot@v0.19.0/src/schemas/tuple/utils/index.ts": "c4bdec56077ab55b0dd4a54ead31ade3dfcd94761cc7cb69a0c8afe1157011a9", - "https://deno.land/x/valibot@v0.19.0/src/schemas/undefinedType/index.ts": "f7fbc79aaa225cdc670469c87de904db72a2b83167c7f4c6f25e5aa1e5f0afcd", - "https://deno.land/x/valibot@v0.19.0/src/schemas/undefinedType/undefinedType.ts": "1c161dc4439e56a64b4ff9009ec15243f5506af5f35d84da3ea4c919c917d6b7", - "https://deno.land/x/valibot@v0.19.0/src/schemas/undefinedType/undefinedTypeAsync.ts": "da7685b1d7ae13382ae7af986c9713df59dfa0fd97fdf6dbaf1580d26a3f7c0a", - "https://deno.land/x/valibot@v0.19.0/src/schemas/union/index.ts": "cc4a28b5dcb866831bbcdb63fc16c179d6340f0d47856f38a3194e213754c230", - "https://deno.land/x/valibot@v0.19.0/src/schemas/union/union.ts": "e445c5150c35758fcb14a93450008a383d0296c3f16fda3ec8dbbb21cd1131a8", - "https://deno.land/x/valibot@v0.19.0/src/schemas/union/unionAsync.ts": "8757d7991fbd1b48bcdc5f50397f48d4fb6f1769dc9566af2755c95f83b2c1e1", - "https://deno.land/x/valibot@v0.19.0/src/schemas/unknown/index.ts": "10361f5acf37be09d4762a1050df606792e89bbecb7e5ad3897e1f44fecbb424", - "https://deno.land/x/valibot@v0.19.0/src/schemas/unknown/unknown.ts": "a90c99174c5a200b7fdac216db6a14ae52338e72c9289a17d71820b440a976c1", - "https://deno.land/x/valibot@v0.19.0/src/schemas/unknown/unknownAsync.ts": "bf5a7700c1c286eac2d7845fd4067a0bd800bf57a050e12aa440d9d2a95b378e", - "https://deno.land/x/valibot@v0.19.0/src/schemas/voidType/index.ts": "fcfabeb131eb3dd34f551177d250211ac246d002b1ed7fd69e9292bfd44a61e9", - "https://deno.land/x/valibot@v0.19.0/src/schemas/voidType/voidType.ts": "40d77da5d274543a415210453ffb240898e2d1ab75a0864c81a7c17d2998e38e", - "https://deno.land/x/valibot@v0.19.0/src/schemas/voidType/voidTypeAsync.ts": "2a6da8b1ea687c394a393480b4dfded5bc415c9b6b1c48c2dc3e9fd6d3a9bd61", - "https://deno.land/x/valibot@v0.19.0/src/transformations/index.ts": "2556f72e5031ddb919e472fe591380349e515dc2bed32cf7234eb1e24de594c0", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toCustom/index.ts": "ced6ce683906043c915c47b9bc3c37760568e8731234ed52378cc0ed297dfc27", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toCustom/toCustom.ts": "2adcf91151f1242d7e583b46ed5fc694b4f86eec712b044a85945b75d70a110b", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toCustom/toCustomAsync.ts": "1aee879199717038b3f7ec666084b923d8a4ad6658220688648f8b88783fa8d1", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toLowerCase/index.ts": "240601776aafe99170f1d5f541156eb47ef6b1de18e4bc1fb0f3c9ccaf2a2ebe", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toLowerCase/toLowerCase.ts": "2374759427fad9a546e6d88845f2ac92b0cbf818a434f011aa815eb9c41789af", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toMaxValue/index.ts": "292a34e8f854111175c370760c63a0b3d4e64a0f52676f649efabc0eb65d992a", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toMaxValue/toMaxValue.ts": "563d58c62b82342e1525b05c5c64fc771abf641ee8fb7d2ce414179a4009a907", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toMinValue/index.ts": "74532e7ca7389896c2b832852ad1730107d70c70ed06644184b5f5e02a06e9da", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toMinValue/toMinValue.ts": "f327e060f995d5670ea3df9d762c3c51c8538eb63b3711280dfda493461b161e", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toTrimmed/index.ts": "63e67ebb0cb8e2840fcced3acf728c63cab84f60bb23dd4a407fcb3d6f28be06", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toTrimmed/toTrimmed.ts": "88a2d8716b23dbfcd8ba2a63cb08835c868bd9526d34503ec5272b6a4a2df09d", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toTrimmedEnd/index.ts": "24893f44e8d38ab65d254c95c8497ca073f592d0ba364b7b10e5b2306d0a1add", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toTrimmedEnd/toTrimmedEnd.ts": "d0d2c85a01e6a8413f692887dc83504b59760eceee208f3a7bd200b8ebe4f2ef", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toTrimmedStart/index.ts": "4c392b44580651367853ff74eba47393b02e40f2241583e126fca8088cfc822b", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toTrimmedStart/toTrimmedStart.ts": "c530953dce8cfe37cb9932ca2100c0ea7ca4c656dcb1e35d8f5e646e33a30efb", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toUpperCase/index.ts": "cb5035ed9e693199a85ddf969fa4d1b7d9325084bb584a2b531617e6da9a1617", - "https://deno.land/x/valibot@v0.19.0/src/transformations/toUpperCase/toUpperCase.ts": "614533b00e33d3153b297cd3107a1f1d9da4c172b071d8069928512fd0d87d5e", - "https://deno.land/x/valibot@v0.19.0/src/types.ts": "7a94caa50f18c5d4ddab1b8bc2200e4bb0bd95ff67c92cd4f979966815c3bae7", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/executePipe.ts": "372b737c79743bf46c8775253dfc2755331b91cd49797c2854068229ca1cd789", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/executePipeAsync.ts": "55c24fe25764c993c574de025d8116715ed0fd877f73c49a0996cfa5f791657f", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/index.ts": "da7634526bff9387de2f5759c6eb3dbaef2e98070833f229676617b9e685899f", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/utils/getIssue/getIssue.ts": "a60cec63fc17e0ec6f81c517650e8f6e37a9fda87ce09c62070270b9ce5d5844", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/utils/getIssue/index.ts": "a660c06c3e0e878935391f55e3c47e701134d84757b07a8c7fb3796104898a80", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/utils/getPipeInfo/getPipeInfo.ts": "5061dd371931bee4e0566e6c045d5f6cf45d60979e6b07a83d8b20c53c199041", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/utils/getPipeInfo/index.ts": "1608abf48f272134e590ae9581523b6ad6d6620bd35cdd984d557c9b41c435e9", - "https://deno.land/x/valibot@v0.19.0/src/utils/executePipe/utils/index.ts": "49ce80c6cc37a004b7ff747ba1e2fd4bca640d815b30296b988d11bbced13955", - "https://deno.land/x/valibot@v0.19.0/src/utils/getDefaultArgs/getDefaultArgs.ts": "76aa88073faf9b87c3e0a90d7986be779df50c98d0219ff37d6731466c947879", - "https://deno.land/x/valibot@v0.19.0/src/utils/getDefaultArgs/index.ts": "37bf8cd3db794d1926b21b3ad9b1696fe13dcb47e8e66d1f2e75fd38bb73a3ee", - "https://deno.land/x/valibot@v0.19.0/src/utils/getErrorMessage/getErrorMessage.ts": "8757ce98b21be9a667c580bc8abd8f70705dc609edd2d71a686740029409c9f1", - "https://deno.land/x/valibot@v0.19.0/src/utils/getErrorMessage/index.ts": "f41a9a223af7091649a2af16fec0a517786e42334777081d97edf515ae0872ff", - "https://deno.land/x/valibot@v0.19.0/src/utils/getIssues/getIssues.ts": "4db6bffc3fa0e2512b82bce90ffc71cc55bbbbb9fcb3d3e3ee4eb7ec3cdd7c31", - "https://deno.land/x/valibot@v0.19.0/src/utils/getIssues/index.ts": "d5f500081372ba51076b7bc2a647e779d9e56d1d95bc86123b0e54826046a519", - "https://deno.land/x/valibot@v0.19.0/src/utils/getOutput/getOutput.ts": "eaaa2b7c9233ddb7fe3af1f46fe7c468410d4a61b5665783ac6ad2ad9dec7d80", - "https://deno.land/x/valibot@v0.19.0/src/utils/getOutput/index.ts": "fb28e27b4e0f2f1b6942503cc16605451b20e646ba6dd61527d99952087cd240", - "https://deno.land/x/valibot@v0.19.0/src/utils/getPipeIssues/getPipeIssues.ts": "8a57ff2c93cbcdeeff1be0cf69d575ac2b41a40b3a42610749a22fc8f1b61011", - "https://deno.land/x/valibot@v0.19.0/src/utils/getPipeIssues/index.ts": "a0d181569d117a37a90b4652d41cfd53ece2bc48f0514c4cdb6d323ea7f234a1", - "https://deno.land/x/valibot@v0.19.0/src/utils/getSchemaIssues/getSchemaIssues.ts": "1369a4427934076b0e5e46e658d68589a320097db44f391466151a63b5a2a16d", - "https://deno.land/x/valibot@v0.19.0/src/utils/getSchemaIssues/index.ts": "964ffb5c642b824f4201326ed6a6f553f33379bf5c7a2894a6d5d51d4533f40c", - "https://deno.land/x/valibot@v0.19.0/src/utils/index.ts": "51368ce8b27cdaf4836e40a453659e59e856cfbd787e98aa2b8808b6b00b511d", - "https://deno.land/x/valibot@v0.19.0/src/utils/isLuhnAlgo/index.ts": "826829a7d1784681a261796c13b71148184536a4c1c1ddf62f5a861e1b9fa190", - "https://deno.land/x/valibot@v0.19.0/src/utils/isLuhnAlgo/isLuhnAlgo.ts": "f313c5c9891a8450dc22777ecf2605084bb2c035dfee81cd130721b10dead6bf", - "https://deno.land/x/valibot@v0.19.0/src/validations/bytes/bytes.ts": "8feb59a00f53aab5c0db8eed920f43b23c4be14ccf3f310aaa7316d4b30d40fa", - "https://deno.land/x/valibot@v0.19.0/src/validations/bytes/index.ts": "d42afa8054800f1eea871aef06f957c757aa40af6b7409f41aa10e925f23ff35", - "https://deno.land/x/valibot@v0.19.0/src/validations/cuid2/cuid2.ts": "67c09c21f39a358a739eb2a45d0c61f6b78be351f5ad3788ca1cc9ae450ea07f", - "https://deno.land/x/valibot@v0.19.0/src/validations/cuid2/index.ts": "b157ea18c33e5ea2e7f2d67c9d328313de948597f1f685ffbc9b019dccb19290", - "https://deno.land/x/valibot@v0.19.0/src/validations/custom/custom.ts": "2122e44a8d77f95546058c0391934e75b6b98b5e9e25aae94d06cb27114edef0", - "https://deno.land/x/valibot@v0.19.0/src/validations/custom/customAsync.ts": "f568491b4ec039b9c74d4ff658a02270e65f5d4d10e6c1c940de55391eeb23b8", - "https://deno.land/x/valibot@v0.19.0/src/validations/custom/index.ts": "b2e137dfd7af4258fd635173bea90539ddaba81a925bfbb806d4189043aae2fc", - "https://deno.land/x/valibot@v0.19.0/src/validations/email/email.ts": "afa8f2386450a62db6b839ffc9e40d9d3eab3bd9580203f017d1c8f1f4fe2fe0", - "https://deno.land/x/valibot@v0.19.0/src/validations/email/index.ts": "77f6fcd59128ddf81f33c662b5fa687d95652f6791d12e6f419474bc15a5dbc5", - "https://deno.land/x/valibot@v0.19.0/src/validations/emoji/emoji.ts": "86dded2656a3f61e575c30afa57766bc2a52f8f240d56116b2e2d1bf6c34048a", - "https://deno.land/x/valibot@v0.19.0/src/validations/emoji/index.ts": "81eefc5fb8a6306aea94c0a4d6b7fcf70982c435366464768276b6af24b59c51", - "https://deno.land/x/valibot@v0.19.0/src/validations/endsWith/endsWith.ts": "d54349afac2df0250839e09cafaa78a5c0edf592e1a393f129adf17e20ae446c", - "https://deno.land/x/valibot@v0.19.0/src/validations/endsWith/index.ts": "896851359dd0c23387c5c1c3fc5048528ef57ed9881028f4c89c58162aa6e604", - "https://deno.land/x/valibot@v0.19.0/src/validations/equal/equal.ts": "9b33da9dd6db8ae5f11d561038eccbfeaa27a5ccbdb514de343deeea3fc1c88d", - "https://deno.land/x/valibot@v0.19.0/src/validations/equal/index.ts": "23b41ec45acd21ff1c549688ba0d3849498b8810bcf6f224813b1236b4ea5a66", - "https://deno.land/x/valibot@v0.19.0/src/validations/excludes/excludes.ts": "b487d1855804a2d70776209a3e77fb09bb7e0b93950d0e7d7d37cb2e5457cf1c", - "https://deno.land/x/valibot@v0.19.0/src/validations/excludes/index.ts": "5ad9e320e89c9d012ff5be9b3760b33f6c3e261a5f24f979d2da3f4cd23fbeb8", - "https://deno.land/x/valibot@v0.19.0/src/validations/finite/finite.ts": "24a2466df0c34d0f94a38dbdc0760d87de82a49774967a11cf0e85e0b87f86b3", - "https://deno.land/x/valibot@v0.19.0/src/validations/finite/index.ts": "e705573032e827d2c70544ff01ee970001dce16d4afcac91f4aa0fb985ba06ed", - "https://deno.land/x/valibot@v0.19.0/src/validations/imei/imei.ts": "213b2e529222801360c8d04cb8498a5cc569f14c5ed57ff430aa9412fb1436d7", - "https://deno.land/x/valibot@v0.19.0/src/validations/imei/index.ts": "4a5d4bcf8a9a18e10e32c4fc6de3690f33a3b1c231a90ab6ff4ad8c1c589596f", - "https://deno.land/x/valibot@v0.19.0/src/validations/includes/includes.ts": "8ada283a7f88efbd372aba255b537b2b2853b30b1b9ffbea08a192bb237e4591", - "https://deno.land/x/valibot@v0.19.0/src/validations/includes/index.ts": "0b0cdd38157c84347260020127a6d1f461d823b374f69e050e27eb675e28ee35", - "https://deno.land/x/valibot@v0.19.0/src/validations/index.ts": "5c6947ad1076e376c4242f0d939262233f24a3a1f583f8730ba2e73ac4c6fcd2", - "https://deno.land/x/valibot@v0.19.0/src/validations/integer/integer.ts": "9eceb76a84d8fbe9d7c4d4bb8e03a376406f420e6fc4139f203e10ad61602285", - "https://deno.land/x/valibot@v0.19.0/src/validations/ip/index.ts": "8facfbc3cc183194dcc000b0dc030bd917ce292d7f2a2a71c801ce77ff8e6cc2", - "https://deno.land/x/valibot@v0.19.0/src/validations/ip/ip.ts": "22695a2c71e84cd0ef9ad646996d41451523737688a92a5139e1a1cb0e8d98cf", - "https://deno.land/x/valibot@v0.19.0/src/validations/ipv4/index.ts": "48105c37b9ec100d49ca0f5abaaa708d8c029d7df50cd28c518a015284fd8b1d", - "https://deno.land/x/valibot@v0.19.0/src/validations/ipv4/ipv4.ts": "5de96f71dfbbe93b97ef778d5f5fa10a6bee9ab7797ec155ea55881f0f89b1dd", - "https://deno.land/x/valibot@v0.19.0/src/validations/ipv6/index.ts": "2ab7903d078c35e0d15f78404e26b99252a5fa3448422ed971b8ef12a2911bc4", - "https://deno.land/x/valibot@v0.19.0/src/validations/ipv6/ipv6.ts": "37c92dc949e64587d3e44ef141873df3552878e60b349015a3733073eee0dabd", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoDate/index.ts": "1dca1733a472c3a21244ed4a395382a7836d5d5d1e5ab7a8d8d85b47053db07d", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoDate/isoDate.ts": "37e899113e55f9dbd878d10e685f5510cc3cb2a04fc20c099e79844f68428f96", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoDateTime/index.ts": "7e55bf21ff01fd4178d38ec00d9e213f4ddf66444e9a1273f8b9e11c3c546919", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoDateTime/isoDateTime.ts": "759979975b974b1e39b139a9f8dcb605e34548ae13c8f324ea4c50514f30730f", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoTime/index.ts": "7d61413b21228bb4ad645af32fae401a7a32e93613b7d8b0fcc266d37ef1a96b", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoTime/isoTime.ts": "002b4e2bf8d41096173b73f8102b0e8109cab1d0dd9d48cc9207a761f803f3d8", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoTimeSecond/index.ts": "b3a2ce5ca284f099773cef0c417bb196403c573e3f14c5fe6facf02b84f3f8a9", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoTimeSecond/isoTimeSecond.ts": "c227f51087d7e8448e2761125f4c8733d7d2b8b43a285d199fb2f37511f94d09", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoTimestamp/index.ts": "d71da9e8fd34761ba29b56a14c1bb1745aee1b2629ba2b7e8a39386ea14793b3", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoTimestamp/isoTimestamp.ts": "7aaeac89ee2a54d9ae9e2f443cd7eb53672fb211ec19b26468e2e3900bbcf86e", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoWeek/index.ts": "0ab75dc237decd1e17aef15a0df6a758ee78b87e6a66119be6ea53292e157ce4", - "https://deno.land/x/valibot@v0.19.0/src/validations/isoWeek/isoWeek.ts": "0ecfa451ed24b78d4971566ef8899d0645b66533a7e6ef7d1ffa2394fd46514c", - "https://deno.land/x/valibot@v0.19.0/src/validations/length/index.ts": "807ae680bfa79db291e59b52daf8bc7cf5a86a73dc79800f57868258188b5868", - "https://deno.land/x/valibot@v0.19.0/src/validations/length/length.ts": "8b2faa923c4db6ff21acdd63ceb66868a266506524c31eb14ae06ec8496c49a2", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxBytes/index.ts": "820623df62598cc942c363a297a51e211dcd24dffe1ac9a6674a5e92cc367bab", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxBytes/maxBytes.ts": "73515c303059f283b90e8ce0b10a6901d79a9d4023d8a35dd3bf8e9e4618d399", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxLength/index.ts": "7a37fbd0adf3004b728eac5b034a3a750ed1d3297be653ae3da17b71abf2227b", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxLength/maxLength.ts": "0b6757b46d509234041118aae0540b94c13c83e03fad39955fc2e68a05952475", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxSize/index.ts": "c90d5fea12ad302bd837d8c9dad6e0d0100551097f4c69cddbd5f81af84b46cc", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxSize/maxSize.ts": "93a36e03d1803feae6f6e7bd48f5e2470de7cf20f33d2e77076d6da68fe1396a", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxValue/index.ts": "5fb5c3f5086a43bb479dd6a3ce20ed58d9bc9c4f0afc8b2c40e572ef3023d0fb", - "https://deno.land/x/valibot@v0.19.0/src/validations/maxValue/maxValue.ts": "c95f81717be54b2183db3a8197fee46dd01210b74217694c3d67a538d27d36ab", - "https://deno.land/x/valibot@v0.19.0/src/validations/mimeType/index.ts": "50600be4af750f5fbbd2c5fbba95e3228507404fac09c334355126bdb2fc8c35", - "https://deno.land/x/valibot@v0.19.0/src/validations/mimeType/mimeType.ts": "40c37458e53d94a69625f320657a631d5d7353c5a985b9a715c0e57783b643fb", - "https://deno.land/x/valibot@v0.19.0/src/validations/minBytes/index.ts": "b32473a1b4c849fb3b7ab44c5081c63791209b51a8cea3a10c05b84362e7f74c", - "https://deno.land/x/valibot@v0.19.0/src/validations/minBytes/minBytes.ts": "22e474ab3891bf2eaa1cd4ca5b57f57b64f620d6225d34d59e615d7fa47b3219", - "https://deno.land/x/valibot@v0.19.0/src/validations/minLength/index.ts": "0520d71de0e292600977a600ef4c72ed2655f798de6d99b9ae32ab011c8bf659", - "https://deno.land/x/valibot@v0.19.0/src/validations/minLength/minLength.ts": "26c63b4052ae2d3552b04a30a9005f6627432f6852d183ddbc722b8a51155cac", - "https://deno.land/x/valibot@v0.19.0/src/validations/minSize/index.ts": "aef9028cee8baf9f7a3d4adcedc9273fc515bdfd39ce242df0b95bfe0bd51428", - "https://deno.land/x/valibot@v0.19.0/src/validations/minSize/minSize.ts": "7aa20fa546bae06dbfd0a04f3f7fc0d25cc5e1c21cd6170022e9809b54d9a5fd", - "https://deno.land/x/valibot@v0.19.0/src/validations/minValue/index.ts": "1a598ee4ec9cafbef8e029b5d47344d56b1789cdb5ce6006a72314483dd23a3e", - "https://deno.land/x/valibot@v0.19.0/src/validations/minValue/minValue.ts": "1fd5e61f1edc8ab73e674c8f471d0899661dc44d80c9382df64506c8a847dffe", - "https://deno.land/x/valibot@v0.19.0/src/validations/multipleOf/index.ts": "a95bda01f2f16d03ecbeec1cbddc831c303ff30871a69ca8107b84bfa4ca462f", - "https://deno.land/x/valibot@v0.19.0/src/validations/multipleOf/multipleOf.ts": "e5a516a2781663732b69e8b60e043514632a594e14222e93b28bb2bc9230d292", - "https://deno.land/x/valibot@v0.19.0/src/validations/notBytes/index.ts": "a94cfb24b9607a558ef10e984c4d90391a27e9e24a2e899acd35f07fa3f82846", - "https://deno.land/x/valibot@v0.19.0/src/validations/notBytes/notBytes.ts": "53bef120abf97999b644e31a6b2cefd123c2f182566da2c0dabefe3762b12892", - "https://deno.land/x/valibot@v0.19.0/src/validations/notLength/index.ts": "410036e126ef4ccbccbf49c0e525482939864cb000fe4d434ae6d83fb28ff66a", - "https://deno.land/x/valibot@v0.19.0/src/validations/notLength/notLength.ts": "ed4767fbce20956c602659e1878a09da5ac10bdda506c65bac080ddaa20d97ef", - "https://deno.land/x/valibot@v0.19.0/src/validations/notSize/index.ts": "e05e9bd6b9e569d6b0a1b6551294bec3d6a898826f15a4c463588de9170465b5", - "https://deno.land/x/valibot@v0.19.0/src/validations/notSize/notSize.ts": "5d70f99749f29bed6b5040eb5539b9232b7eb71dcfc1cb0186a847a189826885", - "https://deno.land/x/valibot@v0.19.0/src/validations/notValue/index.ts": "ef82cc8d8fe0d24b44a2d1c2a047b295e83835f9b23db20f683671eca899eda0", - "https://deno.land/x/valibot@v0.19.0/src/validations/notValue/notValue.ts": "ce9f12557e517b9ac6d0d289ab736806ca64d9a5deaf7fa663663971ee221cab", - "https://deno.land/x/valibot@v0.19.0/src/validations/regex/index.ts": "f99f0575476f13fc93ea05b50783763f1f659f049146b41623778a9f6aab0d75", - "https://deno.land/x/valibot@v0.19.0/src/validations/regex/regex.ts": "2ecaf18d5d3056e5b6a436bf70373478b99f5d56bf30d8b6bd87bbae891835af", - "https://deno.land/x/valibot@v0.19.0/src/validations/safeInteger/index.ts": "227db55d6d1a89f7296cd32a6764c51366eb39ae6d8985b591e9b48b0d6de58e", - "https://deno.land/x/valibot@v0.19.0/src/validations/safeInteger/safeInteger.ts": "5f3a99405252f4311eb9a4ff35554b64cf89bdef722d8a333bb2f602c89969b0", - "https://deno.land/x/valibot@v0.19.0/src/validations/size/index.ts": "2799b930f333714abe3fe60ec486d585b93254a4b9934bee16adde312d84abba", - "https://deno.land/x/valibot@v0.19.0/src/validations/size/size.ts": "aa96c3f7ca4bc515de9524630ec7ae52b100131bbc3dda609984cd7244aa12d8", - "https://deno.land/x/valibot@v0.19.0/src/validations/startsWith/index.ts": "4c87f2127d975bb46fdb1a5fb440b719d6e6221564bb05ef0c69ce068fbcda03", - "https://deno.land/x/valibot@v0.19.0/src/validations/startsWith/startsWith.ts": "d520624924149b655e4b4ca83cceadb49e46b86b02d8a4fe45ae1f675ec163dc", - "https://deno.land/x/valibot@v0.19.0/src/validations/ulid/index.ts": "038df36e31f484211b309520b3967d4a6b27f2a717a44a156d5b0c57e737e1e0", - "https://deno.land/x/valibot@v0.19.0/src/validations/ulid/ulid.ts": "6ef32bb66be17012fcb0ee14f3c744b89dfe97014a3c9f482a3d74f9788e3a60", - "https://deno.land/x/valibot@v0.19.0/src/validations/url/index.ts": "ba4c867ff9030409269cb6902d652e42cb5494b9eec9eaf74f826491cf4ba4cc", - "https://deno.land/x/valibot@v0.19.0/src/validations/url/url.ts": "17b396adf8b84d24fe00e8fca1906ac9aac69c6325005d6103881dc22b09f45a", - "https://deno.land/x/valibot@v0.19.0/src/validations/uuid/index.ts": "9be5093a4927ec982d32c7806a8ebb4d9080ea5023140cae851969eec4339bb5", - "https://deno.land/x/valibot@v0.19.0/src/validations/uuid/uuid.ts": "b5668c710fcf8abf95049ccacb6571d0f3eae0b498adb1b893a12010e98deef8", - "https://deno.land/x/valibot@v0.19.0/src/validations/value/index.ts": "5d0815e3877c5e4548b222597d37fd277f46044193039c7a42539af34e655914", - "https://deno.land/x/valibot@v0.19.0/src/validations/value/value.ts": "1c8b7f37922f7e0158ecce33fd08da9bc8969d39c5ec97bcd567c84ebd299c4a", - "https://deno.land/x/vite_deno_plugin@v0.9.4/mod.ts": "5211d07d1c7edd954ec04a2b801f78a1b5e2f97de1f0d8ad04192f2696e1acd7", - "https://deno.land/x/vite_deno_plugin@v0.9.4/src/deps.ts": "61673c608a8f90718e62693fdf44ea503234b6ebb830235b3199295510d4646a", - "https://deno.land/x/vite_deno_plugin@v0.9.4/src/importMapPlugin.ts": "9784ba12b412067b53c3191d8717764eed1215fa5daf7644a172992335b55053", - "https://deno.land/x/vite_deno_plugin@v0.9.4/src/types.ts": "17f51b42daff15f6ebc416db03f42d0cee4a66cd1ed73b1383967398647787be", - "https://deno.land/x/vite_deno_plugin@v0.9.4/src/urlImportPlugin.ts": "6d38633a05b2ecd4e700cb3a3d90010c30247cf4e99789c9e5d3c2cd9e224644", - "https://deno.land/x/vite_deno_plugin@v0.9.4/src/utils.ts": "12e8c722ba25913faad24cf474fed692aab2c75be42eaf8314ed4babcfb202aa", - "https://deno.land/x/xhr@0.3.0/mod.ts": "094aacd627fd9635cd942053bf8032b5223b909858fa9dc8ffa583752ff63b20", - "https://esm.sh/v135/axios@1.6.7/denonext/axios.mjs": "f89b508eb857fa88ddc80b49c86c9c22319229d1ce8e0ac6a2e8529690691e8b", - "https://esm.sh/v135/axios@1.6.7/denonext/lib/adapters/http.js": "1072da11f42893e364c3f54d1a9387a0403c381c2cf5ee4be49f4f8c8796ed7b", - "https://esm.sh/v135/axios@1.6.7/denonext/lib/adapters/xhr.js": "c4a60102bb607a85c1088513c93ea78e91370f26b0793acf68dc67921c9bdf7b", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/adapters/adapters.js": "a00a352fc0ad0ceab452cfc609809e4d6a1be15d8e12fbc6cf2d5630a86bd60f", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/axios.js": "b746b61c703255ff4a8b4e246919e6c09ce5cf687435b6877564150247eff254", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/cancel/CancelToken.js": "adc8cdc11e4562f801812d0148bb661d48f43cd24f2b9c1b12d0d8446692352d", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/cancel/CanceledError.js": "7b2288324c1576e338979b67b9e9846dab1e1b9c037da18f4ea89f295a669544", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/cancel/isCancel.js": "a34fe4f2c6734728bbcd1dfac8bcd9f9a3121ff3f58107c0286f4c43cc93a095", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/Axios.js": "50bb433056b442fc2fab733a523e9fa086969604e8f5bd727ce63b9820ad1475", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/AxiosError.js": "459a0bc8e163705a5d5e67e6dd88000dd9713b6ee8eb05052f1a888a8e444afc", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/AxiosHeaders.js": "9b196f4e159f447b4a54be73436f7f40f6a2ada718253538207de0956520286a", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/InterceptorManager.js": "c8190b2fffcad51cbb999f8ec4419d04db91a0681a26b35d305eafbbf178ee24", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/buildFullPath.js": "16efd650fdc1dffc70d5efc3a868e88834778be4df6d26c392f50d18f9cdbdec", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/dispatchRequest.js": "2647a1c907aa578d25035c76a57f044a941c81c26727a12b7284d1276fe0977f", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/mergeConfig.js": "d4cd19c6a134c482e2089b07ede594a8123fa494ea306e865090030b405c7463", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/settle.js": "21c87b4b40ae25a90b42d78334864ae1884a67d8e86128c26a1b3c635a77bb88", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/core/transformData.js": "fad5a71528c7887ae9516db24a15d25174c4de120612ba754ad428dd4ed283e7", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/defaults.js": "87873a9baccddfa287ff9a1dc2842e469cd12f7a31f5ad3ecba2c46a43ba3dc2", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/defaults/transitional.js": "30bceadb07212a2c76992dff6c7ed6ab1d13d09163f813fdb28ce50cc41dcef2", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/env/data.js": "893ac80f4f944cb37d8e50bd6fbcaa16c52f930cc8dcd0b442034c2635ce6230", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/AxiosTransformStream.js": "472e7295a2ebeba1bfba03988f5097cf540b46acae4d5274434e0cb62743af69", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/AxiosURLSearchParams.js": "edea893f95c14396c9af54ce8bd153f90b56fd1f2b8d6658c248e29497c327de", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/HttpStatusCode.js": "609be227e981c40bb140eed688f60c227175dde2dc8f38278b6e866e1d0bb517", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/ZlibHeaderTransformStream.js": "3a5453ee4afb55808ea10c877875edfe4561a0ce669cd5d2233894602511640c", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/bind.js": "c64fed7f23ee0f773d0916f2dfeb7ae126dab3c4bfa7f124ab794f908168a1ef", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/buildURL.js": "9d65dc5c872e1cf7b54291aa493c9fc13141d713cdeb70925424567138b9713c", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/callbackify.js": "1bdd5199d76028dda79f4396e3e1b0e07e118e5ba0af40e5beed1f757de257a0", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/combineURLs.js": "4d999a28e24a837679b2ed6493ab59a9d6013842876bd801b896cc7e49236f11", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/cookies.js": "3184d9454c1f253e7f64d29e9e303b7a55bb777d3b8922e194c8cf088cc1a07f", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/formDataToJSON.js": "1efa7992e69e71c5e999ec2faf8b3157fc495cc5af36c35e161f6d775f0d98fb", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/formDataToStream.js": "f1ac2d4d4741a71f22f457a49845aacc6fb20b3eea1279001e04cffbbe03232d", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/fromDataURI.js": "60c101b3fd020c818e25be965ab3f8683c19754b9e197b3cf6727ff85bf3be66", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/isAbsoluteURL.js": "11f05ab2aa4498a62b8722935cde3d488e5eff33f41b6a76a8b42dcd44c4e707", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/isAxiosError.js": "e5cb61c560946a06fc002a025cd04d21ba589159b50ada92706f058ff084d369", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/isURLSameOrigin.js": "01a1192a09e3b52c2049791046d6d88e220ebfc95b4e50cfb4fe9aef6d8bacd8", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/parseHeaders.js": "8e4e55717d76dd6dcc1fe26268ece09efd3c95895043913a96ca16203ae09d2a", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/parseProtocol.js": "78da53bcc371f9afd852f726e8a97d8593c9bce0d18e6beb2fd94038ee6071ce", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/readBlob.js": "1a2321ca226f2d33c90381cee1bc6c1439e682a5e3a9922b3df431916f498379", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/speedometer.js": "f8e7cdb877b7e598dfb8e13b50a2cfe84587b47a8a2fb2c112ecd0cfa45091f4", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/spread.js": "05e88ad681ff2378c1b9df6a8171b839e13491ad1075b55b489d1e2dfa566008", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/throttle.js": "944cbe5b2511f1cbef0d05107fbec1960eed2dc767875c49a2df2b89fd1f45f2", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/toFormData.js": "44f15f9352c26d62c1668da9c56bcac5cec6e84a8f68ea608d2668a621bbeb2c", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/toURLEncodedForm.js": "f066d8d343043882273622877c9f2391a8c57e9e7fad59d581c16b4a4b20bddf", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/helpers/validator.js": "7fba384ddf52f45b0bdc7a6867480a4c22564d13c160407737f287309605f777", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/platform.js": "d6c8710e8ca2cb9225493429d813f3c262b55b35740d571e8bf493d22be26c43", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/platform/common/utils.js": "f0120882b0242e5b1e2c0a54f02aa440aea6ffbaa6b5ae0ab1446da8eafb2384", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/platform/node.js": "e23e4c627d294d33dc371d1c78bed3f14154bd9b07b3c375e4c85424cba5a6d2", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/platform/node/classes/FormData.js": "6d1ca5894ed6bf637a759b3739f4f406fd246a246b349c0003de55e59b21ffcd", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/platform/node/classes/URLSearchParams.js": "ff5d7cfe2bc708add495ee02685d4e21f50917c9026dc493307d6e54afeae6ed", - "https://esm.sh/v135/axios@1.6.7/denonext/unsafe/utils.js": "1b810c9f69721cf274328e9ae588bc89aad029d049ae8dcdc22806ca0cb1a6f5", - "https://esm.sh/v135/balanced-match@1.0.2/denonext/balanced-match.mjs": "b2f9737a6fb330aedd4a444eb85ba127d757c49032a528400fc0e2efb70ddca3", - "https://esm.sh/v135/brace-expansion@2.0.1/denonext/brace-expansion.mjs": "18893e6a2635096f5baf0f5f1f27279bc5d6c85d01cf73e56c638209458945db", - "https://esm.sh/v135/callsites@3.1.0/denonext/callsites.mjs": "768a3d73a61b6c30abe1b906c668b289f2e17c224367a422002903deeb07505a", - "https://esm.sh/v135/debug@4.3.4/denonext/debug.mjs": "d2ebf776ea77aa7df1b4460eb2a4aab245a9d5b19f11fa1db25f756b350bae9d", - "https://esm.sh/v135/follow-redirects@1.15.5/denonext/follow-redirects.mjs": "ceb8e894a4670d8c12cbeeb27c7e58e2acecb2c6280a63611d3536f088f382ef", - "https://esm.sh/v135/form-data@4.0.0/denonext/form-data.mjs": "48e84ac3b590bc364839367938d7e48ca37615a0c66e56dcc7356c3172ec7790", - "https://esm.sh/v135/inclusion@1.0.1/denonext/inclusion.mjs": "7a283c4d99f87273c0fc569ba3ab163c93fe7d7e2a92c751fec9845c06a811b4", - "https://esm.sh/v135/minimatch@9.0.3/denonext/minimatch.mjs": "f04969775beac683b4951b979f92d72e4f1e972ed1314225968c5ed3be312817", - "https://esm.sh/v135/ms@2.1.2/denonext/ms.mjs": "aa4dc45ba72554c5011168f8910cc646c37af53cfff1a15a4decced838b8eb14", - "https://esm.sh/v135/parent-module@2.0.0/denonext/parent-module.mjs": "f869121bafefded20ba66616dcd70a0f2444f3cb2f445a2f0f038dbb11d5c326", - "https://esm.sh/v135/proxy-from-env@1.1.0/denonext/proxy-from-env.mjs": "5a2113ba20eb6fc83d6d68efd3aa65c4b61f05611da20339b14d165807eccfd7", - "https://esm.sh/v135/vite-plugin-https-imports@0.1.0/denonext/vite-plugin-https-imports.mjs": "aa9b52241526560b22f5e168ade8461664ebab9c0e5d81e9dbbe696a046ee6b3", - "https://esm.sh/vite-plugin-https-imports@0.1.0": "a18697c274a4912799c8b99bf75491a1bbee78f8d7379d63b8ed9bd8f5d7e1c5" - } -} diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 191e72769..000000000 --- a/web/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - BIDS Validator - - -
- - - diff --git a/web/src/App.css b/web/src/App.css deleted file mode 100644 index 352792e57..000000000 --- a/web/src/App.css +++ /dev/null @@ -1,22 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -ul.issues-list { - list-style: none; -} - -button { - margin: 1em; -} diff --git a/web/src/App.tsx b/web/src/App.tsx deleted file mode 100644 index 657eaf044..000000000 --- a/web/src/App.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useState } from "react" -import "./App.css" -import { directoryOpen } from "https://esm.sh/browser-fs-access@0.35.0" -import { fileListToTree, validate } from "../dist/validator/main.js" -import type { ValidationResult } from "../../bids-validator/src/types/validation-result.ts" -import { Collapse } from "./Collapse.tsx" -import { Summary } from "./Summary.tsx" - -function Files({ issues }) { - const unique = new Map(issues.map(({ location, issueMessage }) => { - return [`${location}${issueMessage ?? ''}`, { location, issueMessage }] - })) - return ( -
    - {[...unique.values()].map(({ location, issueMessage }) => { - return
  • {location} - { issueMessage && ` (${issueMessage})` }
  • - })} -
- ) -} - -function Issue({ data }) { - const { code, severity } = data.issues[0] - const ret = [] - for (const [subCode, subIssues] of data.groupBy('subCode')) { - if (subIssues.size === 0) { - continue - } - const label = `${severity}: ${code}` + (subCode === 'None' ? '' : ` (${subCode})`) - ret.push( -
- -
{data.codeMessages.get(code)}
- -

- - Search for this issue on Neurostars. - -

-
-
- ) - } - return ret -} - -function App() { - const [validation, setValidation] = useState() - - async function validateDir() { - const dirHandle = await directoryOpen({ - recursive: true, - }) - const fileTree = await fileListToTree(dirHandle) - setValidation(await validate(fileTree, {})) - } - - let validatorOutput - - if (validation) { - const errorList = [] - const warningList = [] - for (const [code, issues] of validation.issues.filter({ severity: 'error' }).groupBy('code')) { - if (code === 'None') { - continue - } - errorList.push(...Issue({ data: issues })) - } - for (const [code, issues] of validation.issues.filter({ severity: 'warning' }).groupBy('code')) { - if (code === 'None') { - continue - } - warningList.push(...Issue({ data: issues })) - } - validatorOutput = ( - <> - -
-
    - {errorList} - {warningList} -
- - - Save JSON log. - -
- - ) - } else { - validatorOutput = ( - <> -

- Select a{" "} - - BIDS dataset - {" "} - to validate. -

- - - ) - } - - return ( - <> -

BIDS Validator

- {validatorOutput} -
- Note: Selecting a dataset only performs validation. Files are never - uploaded. -
-
Previous version of the BIDS validator (non-schema) available here.
- - ) -} - -export default App diff --git a/web/src/Collapse.css b/web/src/Collapse.css deleted file mode 100644 index 53f501e65..000000000 --- a/web/src/Collapse.css +++ /dev/null @@ -1,101 +0,0 @@ -.wrap-collapsible { - margin-bottom: 1.2rem 0; - text-align: left; - padding: 0.5em; -} - -input[type='checkbox'] { - display: none; -} - -.lbl-toggle { - display: block; - - font-weight: bold; - font-family: monospace; - font-size: 1rem; - text-transform: uppercase; - text-align: center; - - padding: 1rem; - - color: #FFF; - background: #000; - - cursor: pointer; - - border-radius: 7px; - transition: all 0.25s ease-out; -} - -.lbl-toggle:hover { - color: #CCC; -} - -.warning .lbl-toggle { - background: #E69F00; -} - -.error .lbl-toggle { - background: #D55E00; -} - -@media (prefers-color-scheme: light) { - .lbl-toggle { - color: #000; - background: #FFF; - } - - .lbl-toggle:hover { - color: #333; - } - - .warning .lbl-toggle { - background: #E69F00; - } - - .error .lbl-toggle { - background: #D55E00; - } -} - -.lbl-toggle::before { - content: ' '; - display: inline-block; - - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 5px solid currentColor; - vertical-align: middle; - margin-right: .7rem; - transform: translateY(-2px); - - transition: transform .2s ease-out; -} - -.toggle:checked+.lbl-toggle::before { - transform: rotate(90deg) translateX(-3px); -} - -.collapsible-content { - max-height: 0px; - overflow: hidden; - transition: max-height .25s ease-in-out; -} - -.toggle:checked+.lbl-toggle+.collapsible-content { - max-height: 100vh; -} - -.toggle:checked+.lbl-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.collapsible-content .content-inner { - border-bottom: 1px solid rgba(220, 220, 220, .45); - border-bottom-left-radius: 7px; - border-bottom-right-radius: 7px; - padding: .5rem 1rem; - text-align: left; -} \ No newline at end of file diff --git a/web/src/Collapse.tsx b/web/src/Collapse.tsx deleted file mode 100644 index 8d4b6c401..000000000 --- a/web/src/Collapse.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { useId } from "react" - -import "./Collapse.css" - -export function Collapse({ label, children }) { - const id = useId() - return ( -
- - -
-
- {children} -
-
-
- ) -} diff --git a/web/src/Summary.css b/web/src/Summary.css deleted file mode 100644 index bf29d96b0..000000000 --- a/web/src/Summary.css +++ /dev/null @@ -1,19 +0,0 @@ -.summary { - text-align: left; - .column { - float: left; - width: 33.33%; - } - - /* Clear floats after the columns */ - .row:after { - content: ""; - display: table; - clear: both; - } - - ul { - list-style: none; - padding-left: 0; - } -} diff --git a/web/src/Summary.tsx b/web/src/Summary.tsx deleted file mode 100644 index 72d6154a4..000000000 --- a/web/src/Summary.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from "react" -import "./Summary.css" - -const summary = { - "sessions": [], - "subjects": ["01", "02"], - "subjectMetadata": [{ "participantId": "01", "age": 25, "sex": "M" }, { - "participantId": "02", - "age": 27, - "sex": "F", - }], - "tasks": [], - "modalities": ["MRI"], - "secondaryModalities": ["MRI_Structural"], - "totalFiles": 6, - "size": 623891, - "dataProcessed": false, - "pet": {}, - "dataTypes": ["anat"], - "schemaVersion": "0.7.3-dev", -} - -/** - * Format bytes as human-readable text. - * - * @param bytes Number of bytes. - * @param si True to use metric (SI) units, aka powers of 1000. False to use - * binary (IEC), aka powers of 1024. - * @param dp Number of decimal places to display. - * - * @return Formatted string. - */ -function humanFileSize(bytes, si = false, dp = 1) { - const thresh = si ? 1000 : 1024 - - if (Math.abs(bytes) < thresh) { - return bytes + " B" - } - - const units = si - ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] - : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] - let u = -1 - const r = 10 ** dp - - do { - bytes /= thresh - ;++u - } while ( - Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1 - ) - - return bytes.toFixed(dp) + " " + units[u] -} - -export function Summary({ data }) { - return ( - -
-
-

Summary:

-
    -
  • - {data.totalFiles} File{data.totalFiles === 1 ? "" : "s"},{" "} - {humanFileSize(data.size)} -
  • -
  • - {data.subjects.length} Subjects, {data.sessions.length} Sessions -
  • -
  • - Schema version: {data.schemaVersion} -
  • -
-
-
-

Available Tasks:

-
    - {data.tasks.map((task) =>
  • {task}
  • )} -
-
-
-

Available Modalities:

-
    - {data.modalities.map((modality) => ( -
  • {modality}
  • - ))} -
-
-
-
- ) -} diff --git a/web/src/index.css b/web/src/index.css deleted file mode 100644 index af31eeb8c..000000000 --- a/web/src/index.css +++ /dev/null @@ -1,64 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/web/src/main.tsx b/web/src/main.tsx deleted file mode 100644 index 791f139e2..000000000 --- a/web/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - , -) diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2a..000000000 --- a/web/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/web/vite.config.mts b/web/vite.config.mts deleted file mode 100644 index 75dd3736d..000000000 --- a/web/vite.config.mts +++ /dev/null @@ -1,44 +0,0 @@ -import { defineConfig } from "npm:vite@^5.0.10" -import react from "npm:@vitejs/plugin-react@^4.2.1" -import httpsImports from "npm:vite-plugin-https-imports@0.1.0" -import { nodePolyfills } from "npm:vite-plugin-node-polyfills@0.22.0" - -import "npm:react@^18.2.0" -import "npm:react-dom@^18.2.0" - -/** - * Vite plugin to hack a bug injected by the default assetImportMetaUrlPlugin - */ -function workaroundAssetImportMetaUrlPluginBug() { - return { - name: "vite-workaround-import-glob", - transform(src, id) { - if (id.includes('validator/main.js')) { - return src.replace(", import.meta.url", "") - } else { - return null - } - } - } -} - -// https://vitejs.dev/config/ -export default defineConfig({ - base: '', - plugins: [ - workaroundAssetImportMetaUrlPluginBug(), - httpsImports.default(), - react(), - nodePolyfills({ - globals: { Buffer: true } - }) - ], - build: { - // ... other configurations - resolve: { - alias: { - "^npm:": "./node_modules/", // or path to your node_modules directory - }, - }, - }, -})